├── .github └── workflows │ └── workflow.yml ├── .gitignore ├── Makefile ├── README.md ├── antilag.md ├── csqc ├── csdefs.qc ├── csextradefs.qc ├── csprogs.src ├── events.qc ├── hitfeedback.qc ├── hud.qc ├── hud_helpers.qc ├── input.qc ├── main.qc ├── menu.qc ├── pmove.qc ├── profile.qc ├── settings.qc ├── status.qc ├── sui_sys.qc ├── tfx.qc ├── vote.qc └── weapon_predict.qc ├── docs ├── hud_statusbar.txt ├── tfentref.txt ├── tfortmap.txt └── versions.txt ├── generate_ctags.sh ├── menu ├── loadsave.qc ├── main.qc ├── menu.src ├── options.qc ├── options_audio.qc ├── options_basic.qc ├── options_effects.qc ├── options_keys.qc ├── options_video.qc ├── presets.qc ├── quit.qc └── servers.qc ├── menusys ├── mitem_bind.qc ├── mitem_checkbox.qc ├── mitem_colours.qc ├── mitem_combo.qc ├── mitem_console.qc ├── mitem_desktop.qc ├── mitem_edittext.qc ├── mitem_exmenu.qc ├── mitem_frame.qc ├── mitem_menu.qc ├── mitem_slider.qc ├── mitem_spinnymodel.qc ├── mitem_tabs.qc ├── mitems.qc ├── mitems_common.qc └── readme.txt ├── share ├── animate.qc ├── classes.qc ├── common_helpers.qc ├── common_vote.qc ├── commondefs.qc ├── debug.qc ├── defs.h ├── fteextensions.qc ├── mcp_precache.qc ├── physics.qc ├── prediction.qc └── weapons.qc ├── ssqc ├── actions.qc ├── admin.qc ├── boss.qc ├── buttons.qc ├── camera.qc ├── clan.qc ├── client.qc ├── combat.qc ├── commands.qc ├── coop.qc ├── csmenu.qc ├── ctf.qc ├── debug.qc ├── defs.qc ├── demoman.qc ├── doors.qc ├── engineer.qc ├── events.qc ├── extraents.qc ├── flare.qc ├── fo_logic.qc ├── fo_math.qc ├── fo_misc_info.qc ├── functions.qc ├── help.qc ├── helpers.qc ├── hwguy.qc ├── items.qc ├── locfiles.qc ├── login.qc ├── medic.qc ├── menu.qc ├── misc.qc ├── monsters.qc ├── mvdsv.qc ├── plats.qc ├── player.qc ├── progs.src ├── pyro.qc ├── q3.qc ├── q3defs.qc ├── quadmode.qc ├── qw.qc ├── rewind.qc ├── roles.qc ├── rotate.qc ├── scout.qc ├── sentry.qc ├── sniper.qc ├── spect.qc ├── spy.qc ├── status.qc ├── subs.qc ├── teamplay.qc ├── tfort.qc ├── tforthlp.qc ├── tfortmap.qc ├── tforttm.qc ├── time.qc ├── triggers.qc ├── tsoldier.qc ├── vote.qc ├── weapons.qc └── world.qc └── version.sh /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: Compile and Upload 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - staging 8 | 9 | jobs: 10 | build-and-deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v1 14 | - name: Compile 15 | run: | 16 | wget http://fte.triptohell.info/moodles/linux_amd64/fteqcc64 17 | chmod +x fteqcc64 18 | export PATH=$GITHUB_WORKSPACE:$PATH 19 | make 20 | mkdir -p dats/${{ github.ref }} 21 | cp *.dat dats/${{ github.ref }} 22 | - uses: jakejarvis/s3-sync-action@master 23 | with: 24 | args: --acl public-read --follow-symlinks 25 | env: 26 | AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} 27 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 28 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 29 | AWS_REGION: 'ap-southeast-2' 30 | SOURCE_DIR: 'dats/refs/heads/' 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.dat 2 | *.lno 3 | *.exe 4 | *.log 5 | *.patch 6 | fteqcc64 7 | .vscode/tasks.json 8 | .gitignore 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifndef VER 2 | VER := $(shell ./version.sh --version) 3 | endif 4 | 5 | ifndef REV 6 | REV := $(shell ./version.sh --revision) 7 | endif 8 | 9 | all: 10 | fteqcc64 -DVER=\"$(VER)\" -DREV=\"$(REV)\" ./ssqc/progs.src 11 | fteqcc64 -DVER=\"$(VER)\" -DREV=\"$(REV)\" ./csqc/csprogs.src 12 | fteqcc64 -DVER=\"$(VER)\" -DREV=\"$(REV)\" ./menu/menu.src 13 | 14 | clean: 15 | rm -f $(TARGET) qwprogs.lno files.dat progdefs.h 16 | -------------------------------------------------------------------------------- /antilag.md: -------------------------------------------------------------------------------- 1 | FortressOne Lag Compenstation 2 | ========= 3 | FortressOne supports lag compensation to improve player experience when playing across continents or across oceans. 4 | 5 | tl;dr 6 | -- 7 | ``` 8 | rcon sv_antilag 1 9 | rcon localinfo project_weapons on 10 | ``` 11 | 12 | There are two types of lag compensation available, and these are configured at the server level. 13 | 14 | Hitscan weapons 15 | --------------- 16 | Enabled with: ```sv_antilag 1``` (default: 0) 17 | 18 | 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. 19 | 20 | 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. 21 | 22 | To enable, set sv_antilag to 1. 23 | 24 | Projectile weapons 25 | 26 | ------------------ 27 | 28 | - Enabled with: ```localinfo project_weapons on``` (default: off) 29 | - Configurable: ```localinfo project_weapons_max_latency``` (default: 0.1) 30 | 31 | 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. 32 | 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. 33 | 34 | For example: 35 | Rockets travel at 900units / second. 36 | 37 | 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. 38 | 39 | 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. 40 | 41 | 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. 42 | 43 | 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. 44 | 45 | -------------------------------------------------------------------------------- /csqc/csprogs.src: -------------------------------------------------------------------------------- 1 | #pragma target fte_5768 2 | #pragma optimise 3 3 | #pragma flag enable subscope 4 | #pragma flag enable iffloat 5 | #pragma flag enable lo 6 | 7 | #pragma progs_dat "../csprogs.dat" 8 | 9 | #includelist 10 | csdefs.qc 11 | csextradefs.qc 12 | 13 | ../share/debug.qc 14 | profile.qc 15 | ../share/commondefs.qc 16 | ../share/common_helpers.qc 17 | ../share/common_vote.qc 18 | ../share/physics.qc 19 | ../share/weapons.qc 20 | ../share/prediction.qc 21 | ../share/classes.qc 22 | ../share/animate.qc 23 | ../share/mcp_precache.qc 24 | weapon_predict.qc 25 | pmove.qc 26 | tfx.qc 27 | sui_sys.qc 28 | vote.qc 29 | status.qc 30 | settings.qc 31 | menu.qc 32 | main.qc 33 | events.qc 34 | hitfeedback.qc 35 | hud_helpers.qc 36 | hud.qc 37 | input.qc 38 | #endlist 39 | -------------------------------------------------------------------------------- /csqc/hitfeedback.qc: -------------------------------------------------------------------------------- 1 | static entity render_hittexts[40]; 2 | static float num_hittexts, num_render; 3 | static float next_render_update; 4 | 5 | static vector clr_noarmour, clr_reg, clr_friendly; 6 | 7 | static void AddRenderableHittext(entity hittext) { 8 | if (num_render < render_hittexts.length) 9 | render_hittexts[num_render++] = hittext; 10 | } 11 | 12 | void NewHittext(entity hittext) { 13 | num_hittexts++; 14 | AddRenderableHittext(hittext); 15 | } 16 | 17 | static void RenderHitText(entity p) { 18 | const float maxd = 1500, mind = 0; 19 | vector po = p.origin; 20 | vector o = PM_Org(); 21 | 22 | float rem = p.hittext_expires - time; 23 | po.z += (CVARF(fo_hittext_duration) - rem) * CVARF(fo_hittext_speed); 24 | vector direc = normalize(po - PM_Org()); 25 | makevectors(getviewprop(VF_ANGLES)); 26 | 27 | float d = v_forward * direc; 28 | 29 | if(d <= 0 || rem <= 0) 30 | return; 31 | 32 | float diff = vlen(po - o); 33 | if (diff > maxd) 34 | return; 35 | 36 | traceline(o, po, MOVE_NOMONSTERS, p); 37 | if (trace_fraction < 1) 38 | return; 39 | 40 | vector clr; 41 | 42 | if (p.hittext_hitflags & HITFLAG_FRIENDLY) { 43 | clr = clr_friendly; 44 | } else { 45 | if (CVARF(fo_hittext_noarmour) && 46 | (p.hittext_hitflags & HITFLAG_NOARMOUR)) 47 | clr = clr_noarmour; 48 | else 49 | clr = clr_reg; 50 | } 51 | 52 | vector c = project(po); 53 | 54 | vector size = '1 1 1' * CVARF(fo_hittext_size); 55 | size *= (maxd - max(diff, mind)) / (maxd - mind); 56 | 57 | string str = p.hittext_str; 58 | // stringwidth() is technically more correct here, but there's a bunch of 59 | // these and we have to draw them every frame so exploit fixed size font to 60 | // compute inline. 61 | c.x -= strlen(str) * size_x / 2; 62 | 63 | // Fade out 64 | float alpha = CVARF(fo_hittext_alpha); 65 | 66 | float fade_time = max(0.5, CVARF(fo_hittext_alpha) * 0.2); 67 | if (rem < fade_time) 68 | alpha = rem/fade_time * CVARF(fo_hittext_alpha); 69 | 70 | drawstring(c, str, size, clr, alpha, 0); 71 | } 72 | 73 | static void UpdateHitTextList() { 74 | if (time < next_render_update) 75 | return; 76 | 77 | // Convert these periodically 78 | clr_reg = stov(CVARS(fo_hittext_colour)); 79 | clr_noarmour = stov(CVARS(fo_hittext_colour2)); 80 | clr_friendly = stov(CVARS(fo_hittext_colour3)); 81 | 82 | next_render_update = time + 0.1; 83 | num_render = 0; 84 | 85 | int count; 86 | entity* hittexts = find_list(classname, CN_HITTEXT, EV_STRING, count); 87 | 88 | num_hittexts = 0; 89 | for (int i = 0; i < count; i++) { 90 | entity ht = hittexts[i]; 91 | if (time > ht.hittext_expires) { 92 | remove(ht); 93 | continue; 94 | } 95 | 96 | num_hittexts++; 97 | AddRenderableHittext(ht); 98 | } 99 | } 100 | 101 | void RenderHitTexts() { 102 | if (num_hittexts) 103 | UpdateHitTextList(); 104 | 105 | if (CVARF(fo_hittext_enabled)) { 106 | for (int i = 0; i < num_render; i++) 107 | RenderHitText(render_hittexts[i]); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /csqc/input.qc: -------------------------------------------------------------------------------- 1 | void Menu_Cancel(); 2 | void FO_Menu_Game(float); 3 | 4 | // TRUE --> capture input 5 | // FALSE --> pass input on 6 | float(float evtype, float scanx, float chary, float devid) CSQC_InputEvent = { 7 | if (fo_hud_editor || fo_hud_menu_active) { 8 | sui_input_event(evtype, scanx, chary, devid); 9 | float menu_mouse = (fo_hud_menu_active && (CurrentMenu.flags & FO_MENU_FLAG_USE_MOUSE)); 10 | 11 | switch (evtype) { 12 | case IE_KEYDOWN: 13 | if (scanx == K_ESCAPE) { 14 | Menu_Cancel(); 15 | FO_Hud_Editor_Cancel(); 16 | return TRUE; // Always capture escape 17 | } else if (fo_hud_menu_active) 18 | return fo_menu_process_input(CurrentMenu, scanx); 19 | break; 20 | case IE_MOUSEABS: 21 | Mouse.x = scanx; 22 | Mouse.y = chary; 23 | break; 24 | } 25 | 26 | return fo_hud_editor; // capture iff hud-editor 27 | } else if(getHudPanel(HUDP_MAP_MENU)->Display) { 28 | sui_input_event(evtype, scanx, chary, devid); 29 | 30 | switch (evtype) { 31 | case IE_MOUSEDELTA: 32 | return TRUE; 33 | case IE_MOUSEABS: 34 | PrevMouse.x = Mouse.x; 35 | PrevMouse.y = Mouse.y; 36 | Mouse.x = scanx; 37 | Mouse.y = chary; 38 | if (sui_is_held(HUDP_MAP_MENU)) 39 | Hud_MapMenuPanel_Move(Mouse.x - PrevMouse.x, Mouse.y - PrevMouse.y); 40 | return TRUE; 41 | case IE_KEYDOWN: 42 | switch (scanx) { 43 | case K_ESCAPE: 44 | return TRUE; 45 | case K_MOUSE1: 46 | return TRUE; 47 | case K_UPARROW: 48 | vote_selected_index--; 49 | vote_list_offset--; 50 | return TRUE; 51 | case K_DOWNARROW: 52 | vote_selected_index++; 53 | vote_list_offset++; 54 | return TRUE; 55 | case K_PGUP: 56 | vote_selected_index -= 10; 57 | vote_list_offset -= 10; 58 | return TRUE; 59 | case K_PGDN: 60 | vote_selected_index +=10; 61 | vote_list_offset +=10; 62 | return TRUE; 63 | case K_MWHEELUP: 64 | vote_list_offset--; 65 | return TRUE; 66 | case K_MWHEELDOWN: 67 | vote_list_offset++; 68 | return TRUE; 69 | case K_ENTER: 70 | if(vote_selected_item && current_vote == vote_selected_item.owner) { 71 | localcmd("cmd break\n"); 72 | } else { 73 | localcmd("cmd votemap ", vote_selected_item.owner.name, "\n"); 74 | } 75 | return TRUE; 76 | case K_BACKSPACE: 77 | if(strlen(vote_list_filter) > 0) { 78 | vote_list_filter = substring(vote_list_filter, 0, strlen(vote_list_filter) - 1); 79 | ApplyMapFilter(); 80 | } 81 | return TRUE; 82 | case K_DEL: //blank it out 83 | if(strlen(vote_list_filter) > 0) { 84 | vote_list_filter = ""; 85 | ApplyMapFilter(); 86 | } 87 | return TRUE; 88 | default: 89 | //48 = '0' .. 57 = '9' 90 | //97 = 'a' .. 122 = 'z' 91 | //45 = '-' 92 | if((scanx >= 48 && scanx <= 57) || (scanx >= 97 && scanx <= 122) || scanx == 45) { 93 | if(strlen(vote_list_filter) < MAP_MAX_CHARS) { 94 | vote_list_filter = strcat(vote_list_filter, chr2str(chary)); 95 | ApplyMapFilter(); 96 | } 97 | return TRUE; 98 | } 99 | } 100 | case IE_KEYUP: 101 | switch (scanx) { 102 | case K_ESCAPE: 103 | showVoteMenu(FALSE); 104 | return TRUE; 105 | case K_MOUSE1: 106 | return TRUE; 107 | } 108 | } 109 | } else { 110 | switch (evtype) 111 | { 112 | case IE_KEYDOWN: 113 | switch (scanx) 114 | { 115 | case K_ESCAPE: 116 | FO_Menu_Game(TRUE); 117 | return TRUE; 118 | } 119 | break; 120 | default: 121 | } 122 | } 123 | 124 | return FALSE; 125 | } 126 | -------------------------------------------------------------------------------- /csqc/profile.qc: -------------------------------------------------------------------------------- 1 | DEFCVAR_FLOAT(fo_enable_profiling, 0); 2 | 3 | inline float sq(float x) { return x * x; } 4 | 5 | struct perf_samples { 6 | float* values; 7 | int max_count; 8 | int count; 9 | }; 10 | 11 | float perf_add_sample(perf_samples* ps, float v) { 12 | float idx = ps->count++ % ps->max_count; 13 | float old = ps->values[idx]; 14 | ps->values[idx] = v; 15 | return old; 16 | } 17 | 18 | struct moving_avg { 19 | perf_samples samples; 20 | float s1, s2; 21 | }; 22 | 23 | void update_online_avg(moving_avg* avg, float newv) { 24 | float oldv = perf_add_sample(&avg->samples, newv); 25 | 26 | avg->s1 += newv - oldv; 27 | avg->s2 += newv*newv - oldv*oldv; 28 | } 29 | 30 | float read_online_avg(struct moving_avg* avg, float* mean, float* variance) { 31 | float n = max(min(avg->samples.count, 2), avg->samples.max_count); 32 | 33 | *mean = avg->s1 / n; 34 | *variance = (avg->s2 - sq(avg->s1) / n) / (n - 1); 35 | return n; 36 | } 37 | 38 | void compute_avg(struct perf_samples* samples, float* mean, float* variance) { 39 | if (samples->count < 2) { 40 | *mean = samples->values[0]; 41 | *variance = 0; 42 | return; 43 | } 44 | 45 | float K = samples->values[0]; 46 | float i, n, s1, s2; 47 | s1 = s2 = 0; 48 | n = min(samples->count, samples->max_count); 49 | for (i = 0; i < n; i++) { 50 | s1 += samples->values[i] - K; 51 | s2 += sq(samples->values[i] - K); 52 | } 53 | 54 | *mean = s1/n + K; 55 | *variance = (s2 - sq(s1) / n) / (n - 1); 56 | } 57 | 58 | void compute_maxmin(struct perf_samples* samples, float* minv, float* maxv) { 59 | *minv = *maxv = 0; 60 | 61 | if (samples->count > 0) 62 | *minv = *maxv = samples->values[0]; 63 | 64 | for (float i = 1; i < min(samples->max_count, samples->count); i++) { 65 | *minv = min(*minv, samples->values[i]); 66 | *maxv = max(*maxv, samples->values[i]); 67 | } 68 | } 69 | 70 | // Must orchestrate calling INIT before use. 71 | #define DECLARE_MOVING_AVG(_v, _c) \ 72 | var float _v##_samples[_c]; \ 73 | var moving_avg _v 74 | 75 | #define INIT_PERF_SAMPLES(_ps, _backing) \ 76 | do { \ 77 | (_ps)->values = _backing; (_ps)->max_count = (_backing).length; (_ps)->count = 0; \ 78 | } while (0) 79 | 80 | #define INIT_MOVING_AVG(_v) INIT_PERF_SAMPLES(_v.samples, _v##_samples) 81 | 82 | 83 | struct perf_sampler { 84 | float interval; 85 | perf_samples samples; 86 | 87 | float next_sample; // In game time, we want no exits. 88 | float sample_start; // External [e.g. gettime(1)] based 89 | }; 90 | 91 | float perf_start_sample(perf_sampler* ps, float force = FALSE) { 92 | if (!CVARF(fo_enable_profiling)) 93 | return FALSE; 94 | 95 | if (time < ps->next_sample && !force) 96 | return FALSE; 97 | ps->sample_start = gettime(1); // cltime 98 | return TRUE; // Might return a (1-based) index at some point 99 | } 100 | 101 | void perf_finish_sample(perf_sampler* ps, float idx) { 102 | if (idx == FALSE) 103 | return; // Not sampled. 104 | ps->next_sample = cltime + (random() + random()) * ps->interval; 105 | perf_add_sample(&ps->samples, gettime(1) - ps->sample_start); 106 | } 107 | 108 | #define DECLARE_PERF_SAMPLER(_v, _c, _i) \ 109 | var float _v##_samples[_c]; \ 110 | var perf_sampler _v = { _i } 111 | 112 | #define INIT_PERF_SAMPLER(_v) INIT_PERF_SAMPLES(_v.samples, _v##_samples) 113 | -------------------------------------------------------------------------------- /csqc/settings.qc: -------------------------------------------------------------------------------- 1 | DEFCVAR_FLOAT(fo_grentimer, 2); // Sound + Ping adjust 2 | DEFCVAR_STRING(fo_grentimersound, "grentimer.wav"); 3 | DEFCVAR_FLOAT(fo_grentimervolume, 1); 4 | 5 | // When set (not -1) and spectating, supercedes fo_grentimervolume. 6 | DEFCVAR_FLOAT(fo_spec_grentimervolume, -1); 7 | 8 | DEFCVAR_FLOAT(fo_oldscoreboard, 0); 9 | DEFCVAR_FLOAT(fo_default_weapon, 0); 10 | DEFCVAR_FLOAT(fo_team_color_crosshair, 0); 11 | 12 | DEFCVAR_FLOAT(fo_hitaudio_enabled, 1); 13 | DEFCVAR_FLOAT(fo_hitaudio_hurtself, 1); 14 | DEFCVAR_FLOAT(fo_hitaudio_hurtteam, 1); 15 | DEFCVAR_FLOAT(fo_hitaudio_hurtenemy, 1); 16 | DEFCVAR_FLOAT(fo_hitaudio_killself, 1); 17 | DEFCVAR_FLOAT(fo_hitaudio_killteam, 1); 18 | DEFCVAR_FLOAT(fo_hitaudio_killenemy, 1); 19 | DEFCVAR_FLOAT(fo_hitaudio_noarmour, 1); 20 | 21 | DEFCVAR_FLOAT(fo_hittext_enabled, 1); 22 | DEFCVAR_FLOAT(fo_hittext_size, 16); 23 | DEFCVAR_FLOAT(fo_hittext_speed, 96); 24 | DEFCVAR_FLOAT(fo_hittext_duration, 2); 25 | DEFCVAR_FLOAT(fo_hittext_alpha, 1); 26 | DEFCVAR_FLOAT(fo_hittext_rawdamage, 1); 27 | DEFCVAR_FLOAT(fo_hittext_noarmour, 1); 28 | DEFCVAR_FLOAT(fo_hittext_offset, 32); 29 | DEFCVAR_FLOAT(fo_hittext_friendly, 0); 30 | DEFCVAR_STRING(fo_hittext_colour, "1 1 1"); 31 | DEFCVAR_STRING(fo_hittext_colour2, "1 0 1"); 32 | DEFCVAR_STRING(fo_hittext_colour3, "1 0 0"); 33 | 34 | DEFCVAR_FLOAT(fo_hud_idle_alpha, 0.3); 35 | DEFCVAR_FLOAT(fo_adminrefresh, 2); 36 | 37 | static float __unused; // Needed to avoid a spurious compiler error. 38 | 39 | -------------------------------------------------------------------------------- /csqc/vote.qc: -------------------------------------------------------------------------------- 1 | void () ApplyMapFilter = { 2 | entity mc = find(world, classname, "map_candidate_filtered"); 3 | entity temp = world; 4 | while(mc) { 5 | remove(mc); 6 | mc = find(mc, classname, "map_candidate_filtered"); 7 | } 8 | num_mapvotes_filtered = 0; 9 | mc = find(world, classname, "map_candidate"); 10 | while(mc) { 11 | if(strstrofs(mc.name, vote_list_filter) > -1) { 12 | temp = spawn(); 13 | temp.classname = "map_candidate_filtered"; 14 | temp.owner = mc; 15 | //temp.name = mc.name; 16 | //temp.description = mc.description; 17 | //temp.groupname = mc.groupname; 18 | //temp.group = mc.group; 19 | //temp.team_num = mc.team_num; 20 | //temp.min_val = mc.min_val; 21 | //temp.max_val = mc.max_val; 22 | //temp.localmap = mc.localmap; 23 | num_mapvotes_filtered++; 24 | } 25 | mc = find(mc, classname, "map_candidate"); 26 | } 27 | } 28 | 29 | /* 30 | "name" "amth1" //map name 31 | "description" "Duel arena, ctf and Sniper War" //description 32 | "groupname" "Duel/Arena" //group 33 | "min_val" "2" //min players 34 | "max_val" "8" //max players 35 | "team_num" "4" //supported teams 36 | 37 | .cnt = number of current votes 38 | */ 39 | 40 | entity (string n) AddVoteMapGroup = { 41 | if(!n) { 42 | return world; 43 | } 44 | local entity mg = find(world, classname, "map_group"); 45 | while(mg) { 46 | if(mg.netname == n) { 47 | return mg; 48 | } 49 | mg = find(mg, classname, "map_group"); 50 | } 51 | 52 | mg = spawn(); 53 | mg.classname = "map_group"; 54 | mg.name = n; 55 | return mg; 56 | }; 57 | 58 | entity (string n, string desc, string mapgroup, float num_teams, float min_players, float max_players, float islocal) AddVoteMap = { 59 | if(!n) { 60 | return world; 61 | } 62 | local entity mc = find(world, classname, "map_candidate"); 63 | while(mc) { 64 | if(mc.name == n) { 65 | return mc; 66 | } 67 | mc = find(mc, classname, "map_candidate"); 68 | } 69 | 70 | mc = spawn(); 71 | mc.classname = "map_candidate"; 72 | mc.name = n; 73 | mc.description = desc; 74 | mc.groupname = mapgroup; 75 | mc.group = AddVoteMapGroup(mapgroup); 76 | mc.team_num = num_teams?num_teams:2; 77 | mc.min_val = min_players?min_players:6; 78 | mc.max_val = max_players?max_players:16; 79 | mc.localmap = islocal; 80 | 81 | num_mapvotes++; 82 | ApplyMapFilter(); 83 | return mc; 84 | }; 85 | 86 | float (string n, float isLocal) RemoveVoteMap = { 87 | if(!n) { 88 | return FALSE; 89 | } 90 | local entity mc = find(world, classname, "map_candidate"); 91 | while(mc) { 92 | if(mc.name == n) { 93 | if(isLocal || isLocal == mc.localmap) { 94 | if(current_vote == mc) { 95 | current_vote = world; 96 | } 97 | remove(mc); 98 | num_mapvotes--; 99 | ApplyMapFilter(); 100 | return TRUE; 101 | } else { 102 | return FALSE; 103 | } 104 | } 105 | mc = find(mc, classname, "map_candidate"); 106 | } 107 | return FALSE; 108 | } 109 | -------------------------------------------------------------------------------- /docs/hud_statusbar.txt: -------------------------------------------------------------------------------- 1 | The status bar shows TF-specific info, and it works in different ways depending on the client: 2 | 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. 3 | For ezquake (and basically everything else), it will use a centerprint hack to permanently display the status bar. 4 | 5 | It can be enabled using the client setting 6 | setinfo sb 40 7 | (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. 8 | 9 | A specific sub-component can be enabled using the command 10 | setinfo sbflaginfo 1|2 11 | 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. 12 | 13 | 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 14 | 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. 15 | This does limit the number of trackable items to 4 however. 16 | 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. 17 | 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. 18 | 19 | The sb flaginfo component also displays the "button" countdowns (such as lasers/security being down), if available, and there is room. 20 | This functions in a similar way to the flag info, but, while technically unlimited, 21 | the combined number of all sbar items (flags and buttons) is 10 for fte and 6 for ezquake. 22 | For ezquake, the buttons will be distinguished by name, and will not appear until they are actually triggered and have a countdown active. 23 | For fte, the buttons are distinguished by team colours and will display a different icon to the flag, but otherwise function similarly. 24 | 25 | For a map to allow these to be tracked, the following needs to be set for each button: 26 | - It must be set on an info_tfgoal entity 27 | - It must have the field "track_goal" be set to 1 28 | - It must have a "goal_no" field set 29 | - It must have the field "delay_time" set to the countdown time 30 | - 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. 31 | - For ezquake, it must have a "netname" field set to distinguish the goals. 32 | - For FTE, it should have the "owned_by" field set which will determine the icon colour. 33 | - 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". 34 | 35 | For FTE, the icons for the above are in the same location as the normal hud elements: 36 | ie fortress/textures/wad/ 37 | They are named "flag_" for the flag icons and "off_icon_glow_" for the button icons. 38 | -------------------------------------------------------------------------------- /generate_ctags.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm *tags 4 | fteqcc64 -ftags ./ssqc/progs.src 5 | fteqcc64 -ftags ./csqc/csprogs.src 6 | fteqcc64 -ftags ./menu/menu.src 7 | cat *.tags|LC_COLLATE=C sort>tags 8 | rm *.tags 9 | -------------------------------------------------------------------------------- /menu/loadsave.qc: -------------------------------------------------------------------------------- 1 | #ifndef LOADSAVE_QC 2 | #define LOADSAVE_QC 3 | 4 | //I'm feeling lazy, so I'm going to only provide X slots, like quake's menu. 5 | static string savenames[] = 6 | { 7 | "a0", 8 | "a1", 9 | "a2", 10 | "quick", 11 | "s0", 12 | "s1", 13 | "s2", 14 | "s3", 15 | "s4", 16 | "s5", 17 | "s6", 18 | "s7", 19 | "s8", 20 | "s9", 21 | }; 22 | #define NUMSAVESLOTS savenames.length 23 | 24 | /* 25 | class mitem_savescreeny : mitem 26 | { 27 | virtual void(vector pos) item_draw = 28 | { 29 | string s = sprintf("saves/s%g/screeny.png", selectedsaveslot); 30 | if not(whichpack(s)) 31 | if (drawgetimagesize(s) != '0 0 0') 32 | ui.drawpic(pos, s, item_size, item_rgb, item_alpha, 0); 33 | }; 34 | }; 35 | */ 36 | class mitem_saveoption : mitem_text 37 | { 38 | string slot; 39 | float mode; 40 | 41 | virtual void() mitem_saveoption = 42 | { 43 | if (mode) 44 | item_flags |= IF_SELECTABLE; 45 | }; 46 | 47 | virtual void(vector pos) item_draw = 48 | { 49 | //some sort of pulsing if its active. 50 | if (item_flags & IF_KFOCUSED) 51 | ui.drawfill(pos, item_size, '1 0 0', sin(cltime)*0.125+0.150, 0); 52 | float w = stringwidth(item_text, TRUE, '1 1 0'*item_scale); 53 | ui.drawstring(pos + [(item_size_x-w)/2, 0], item_text, '1 1 0' * item_scale, menuitem_textcolour(this), item_alpha, 0); 54 | }; 55 | 56 | virtual float(vector pos, float scan, float char, float down) item_keypress = 57 | { 58 | if (!down) 59 | return FALSE; 60 | if (scan == K_ENTER || (scan == K_MOUSE1 && mouseinbox(pos, this.item_size))) 61 | { 62 | if (item_flags & IF_KFOCUSED) 63 | { 64 | switch(mode) 65 | { 66 | case 0: 67 | break; //can't load a slot which is empty. 68 | case 1: 69 | localcmd(sprintf("m_pop;load %s\n", slot)); 70 | break; 71 | case 2: 72 | //FTE has a savegame_legacy command if you want compatibility with other engines. 73 | localcmd(sprintf("m_pop;wait;echo \"%s\";save %s\n", _("Saving Game"), slot)); 74 | //localcmd(sprintf("m_pop;wait;screenshot saves/s%g/screeny.png;echo \"%s\";save s%g\n", slot, _("Saving Game"), slot)); 75 | break; 76 | } 77 | } 78 | else 79 | { 80 | item_parent.item_focuschange(this, IF_KFOCUSED); 81 | } 82 | return TRUE; 83 | } 84 | return FALSE; 85 | }; 86 | }; 87 | 88 | class mitem_savepreview : mitem 89 | { 90 | //assumption: the only selectable children in the parent are save options. 91 | virtual void(vector pos) item_draw = 92 | { 93 | mitem_saveoption sel; 94 | sel = (mitem_saveoption)item_parent.item_kactivechild; 95 | if (sel) 96 | { 97 | string s = sprintf("saves/%s/screeny.tga", sel.slot); 98 | if (drawgetimagesize(s) != '0 0 0') 99 | ui.drawpic(pos, s, item_size, item_rgb, item_alpha, 0); 100 | } 101 | }; 102 | }; 103 | 104 | static string(string savename) scansave = 105 | { 106 | string l; 107 | float f = fopen(sprintf("saves/%s/info.fsv", savename), FILE_READ); 108 | if (f < 0) 109 | f = fopen(sprintf("%s.sav", savename), FILE_READ); 110 | if (f < 0) 111 | return __NULL__; //weird 112 | 113 | fgets(f); //should be the version 114 | l = fgets(f); //description 115 | if (l) 116 | l = strreplace("_", " ", l); 117 | fclose(f); 118 | return l; 119 | }; 120 | 121 | void(mitem_desktop desktop, float mode) M_LoadSave = 122 | { 123 | mitem_exmenu m = spawn(mitem_exmenu, item_text:"Load/Save", item_flags:IF_SELECTABLE, item_command:"m_main"); 124 | 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'); 125 | desktop.item_focuschange(m, IF_KFOCUSED); 126 | m.totop(); 127 | 128 | string l; 129 | float i; 130 | float smode; 131 | float pos = NUMSAVESLOTS*16/-2; 132 | 133 | 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); 134 | 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]); 135 | 136 | for (i = 0; i < NUMSAVESLOTS; i++) 137 | { 138 | l = scansave(savenames[i]); 139 | smode = mode; 140 | if (l=="") 141 | { 142 | l = "Empty Slot"; 143 | if (mode==1) 144 | smode = 0; 145 | } 146 | 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]); 147 | } 148 | 149 | m.addm(spawn(mitem_savepreview), [-320, -240], [320, 240]); 150 | addmenuback(m); 151 | }; 152 | 153 | void(mitem_desktop desktop) M_Load = 154 | { 155 | M_LoadSave(desktop, 1); 156 | }; 157 | void(mitem_desktop desktop) M_Save = 158 | { 159 | if (!(isserver() || dp_workarounds)) 160 | 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. 161 | else 162 | M_LoadSave(desktop, 2); 163 | }; 164 | #endif 165 | -------------------------------------------------------------------------------- /menu/main.qc: -------------------------------------------------------------------------------- 1 | /* 2 | The main / root menu. 3 | Just a load of text with console commands attached. 4 | Choice of buttons available is somewhat dynamic. 5 | 6 | There's also some generic kludge crap in here, like menu background tints 7 | */ 8 | 9 | /* 10 | Adds a background tint to a (typically) exmenu parent. 11 | In FTE, we use built-in stuff to give a sepia effect. 12 | In DP, we just tint it black. 13 | */ 14 | nonstatic void(mitem_frame m) addmenuback = 15 | { 16 | if (iscachedpic("menutint")) //fte internal hacks! meh, admit it. its cool. 17 | m.add(spawn (mitem_pic, item_text:"menutint", item_alpha:0.5), 18 | RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, [0, 0], [0, 0]); 19 | else 20 | m.add(spawn (mitem_fill, item_rgb:'0 0 0.01', item_alpha:0.5), 21 | RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, [0, 0], [0, 0]); 22 | }; 23 | 24 | /* 25 | helper functions to avoid blowing up in older clients. 26 | 27 | */ 28 | #define dp(dpc,qwc) (cvar_type(dpc)?dpc:qwc) 29 | float(string cmd) assumetruecheckcommand = 30 | { 31 | if (!checkextension("FTE_QC_CHECKCOMMAND")) 32 | return TRUE; 33 | return checkcommand(cmd); 34 | }; 35 | float(string cmd) assumefalsecheckcommand = 36 | { 37 | if (!checkextension("FTE_QC_CHECKCOMMAND")) 38 | return FALSE; 39 | return checkcommand(cmd); 40 | }; 41 | 42 | 43 | 44 | nonstatic void(mitem_desktop desktop) M_Main = 45 | { 46 | local float y; 47 | local mitem_exmenu m; 48 | 49 | //no dupes please. 50 | m = (mitem_exmenu)desktop.findchildtext(_("Main Menu")); 51 | if (m) 52 | { 53 | m.totop(); 54 | return; 55 | } 56 | 57 | //create a fullscreen frame 58 | m = spawn(mitem_exmenu, item_text:_("Main Menu"), item_flags:IF_SELECTABLE); 59 | 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'); 60 | desktop.item_focuschange(m, IF_KFOCUSED); 61 | m.totop(); 62 | 63 | // m.item_flags |= IF_NOKILL; 64 | // m.adda(menuitempic_spawn ("gfx/qplaque.lmp", '32 144'), '16 4'); 65 | 66 | y = 7*-16/2; 67 | 68 | //draw title art above the options 69 | mitem_pic banner = spawn(mitem_pic, item_text:"gfx/ttl_main.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); 70 | 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]); 71 | 72 | 73 | //a macro, in a desperate attempt at readability 74 | #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]) 75 | 76 | #ifdef CSQC 77 | if (serverkey("constate") != "disconnected") {menuitemtext_cladd16(m, _("Return To Game"), "m_pop", y); y += 16;} 78 | if (serverkey("constate") != "disconnected") {menuitemtext_cladd16(m, isserver?_("End Game"):_("Disconnect"),"m_pop;disconnect", y); y += 16;} else 79 | #endif 80 | #ifdef CSQC 81 | if (checkextension("FTE_CSQC_SERVERBROWSER")) 82 | #endif 83 | {menuitemtext_cladd16(m, _("Join Server"), "m_pop;m_servers", y); y += 16;} 84 | if (assumefalsecheckcommand("menu_demo")) {menuitemtext_cladd16(m, _("Demos"), "m_pop;menu_demo", y); y += 16;} 85 | //if (assumetruecheckcommand("save") && (isserver()||dp_workarounds)) {menuitemtext_cladd16(m, _("Save"), "m_pop;m_save", y); y += 16;} 86 | //if (assumetruecheckcommand("load")) {menuitemtext_cladd16(m, _("Load"), "m_pop;m_load", y); y += 16;} 87 | //if (assumefalsecheckcommand("cef")) {menuitemtext_cladd16(m, _("Browser"), "m_pop;cef google.com", y); y += 16;} 88 | //if (assumefalsecheckcommand("xmpp")) {menuitemtext_cladd16(m, _("Social"), "m_pop;xmpp", y); y += 16;} 89 | //if (assumefalsecheckcommand("irc")) {menuitemtext_cladd16(m, _("IRC"), "m_pop;irc /info", y); y += 16;} 90 | //if (assumefalsecheckcommand("menu_download")) {menuitemtext_cladd16(m, _("Updates+Packages"), "m_pop;menu_download", y); y += 16;} 91 | if (assumefalsecheckcommand("qi")) {menuitemtext_cladd16(m, _("Quake Injector"), "m_pop;qi", y); y += 16;} 92 | {menuitemtext_cladd16(m, _("Options"), "m_pop;m_options", y); y += 16;} 93 | {menuitemtext_cladd16(m, _("Quit"), "m_pop;m_quit", y); y += 16;} 94 | 95 | #if 1//def CSQC 96 | //spinny quad/pent, for the luls 97 | //local string it = (random()<0.9)?"progs/quaddama.mdl":"progs/invulner.mdl"; 98 | local string skin = "sol"; 99 | local float r = random(), col = 13; //Blue 100 | 101 | if(r > 0.89) 102 | skin = "sco"; 103 | else if(r > 0.78) 104 | skin = "sni"; 105 | else if(r > 0.67) 106 | skin = "dem"; 107 | else if(r > 0.56) 108 | skin = "med"; 109 | else if(r > 0.45) 110 | skin = "hwg"; 111 | else if(r > 0.34) 112 | skin = "pyr"; 113 | else if(r > 0.23) 114 | skin = "spy"; 115 | else if(r > 0.12) 116 | skin = "eng"; 117 | 118 | //skin = "player_red"; 119 | 120 | r = random(); 121 | if(r > 0.95) { 122 | col = 11; //Green 123 | skin = strcat("gren_", skin); 124 | } else if(r > 0.9) { 125 | col = 12; //Yellow 126 | skin = strcat("yell_", skin); 127 | } else if(r > 0.45) { 128 | col = 4; //Red 129 | skin = strcat("red_", skin); 130 | } else { 131 | skin = strcat("blue_", skin); 132 | } 133 | 134 | //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]); 135 | m.add(spawn (mitem_spinnymodel, 136 | item_text: "progs/player.mdl", 137 | firstframe:12, 138 | framecount:5, 139 | shootframe:119, 140 | shootframes:6, 141 | dontrotate:1, 142 | startangle:'0 155 0', 143 | customskin:skin, 144 | rotatespeed:10, 145 | topcolour:col, 146 | bottomcolour:col 147 | ), 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]); 148 | #else 149 | //menuqc doesn't support entities. shove some random crappy static image there instead. 150 | local mitem_pic plaque = spawn (mitem_pic, item_text:"gfx/qplaque.lmp", item_alpha:1); 151 | m.addm(plaque, [(-160-plaque.item_size_x)*0.5, 12*-16/2], [(-160+plaque.item_size_x)*0.5, 12*16/2]); 152 | #endif 153 | 154 | addmenuback(m); 155 | }; 156 | -------------------------------------------------------------------------------- /menu/menu.src: -------------------------------------------------------------------------------- 1 | #pragma target fte_5768 2 | #pragma optimise 3 3 | #pragma flag enable subscope 4 | 5 | #pragma progs_dat "../menu.dat" 6 | 7 | #define MENU //select the module 8 | 9 | #includelist 10 | ../share/fteextensions.qc //also sets up system defs 11 | ../share/debug.qc 12 | 13 | ../menusys/mitems.qc //root item type 14 | ../menusys/mitems_common.qc //basic types 15 | ../menusys/mitem_desktop.qc //other sort of root item 16 | ../menusys/mitem_exmenu.qc //fullscreen/exclusive menus 17 | ../menusys/mitem_edittext.qc //simple text editor 18 | ../menusys/mitem_tabs.qc //tabs 19 | ../menusys/mitem_colours.qc //colour picker 20 | ../menusys/mitem_checkbox.qc //checkbox (boolean thingies) 21 | ../menusys/mitem_slider.qc //scrollbars 22 | ../menusys/mitem_combo.qc //multiple-choice thingies 23 | ../menusys/mitem_bind.qc //key binding thingie 24 | ../menusys/mitem_spinnymodel.qc //menu art 25 | #endlist 26 | 27 | //might as well put this here. 28 | 29 | void(mitem_desktop desktop) M_Pop = 30 | { 31 | mitem it = desktop.item_kactivechild; 32 | if (it) 33 | it.item_remove(); 34 | }; 35 | 36 | //define the commands. 37 | //cmd argments are: Name, Function, Sourcefile(may be empty) 38 | #define concommandslist \ 39 | cmd("m_main", M_Main, main.qc) \ 40 | cmd("m_pop", M_Pop, ) \ 41 | cmd("m_options", M_Options, options.qc) \ 42 | cmd("m_keys", M_Options_Keys, options_keys.qc) \ 43 | cmd("m_basicopts", M_Options_Basic, options_basic.qc) \ 44 | cmd("m_video", M_Options_Video, options_video.qc) \ 45 | cmd("m_effects", M_Options_Effects, options_effects.qc) \ 46 | cmd("m_audio", M_Options_Audio, options_audio.qc) \ 47 | cmd("m_load", M_Load, loadsave.qc) \ 48 | cmd("m_save", M_Save, ) \ 49 | cmd("m_quit", M_Quit, quit.qc) \ 50 | cmd("m_servers", M_Servers, servers.qc) \ 51 | cmd("m_reset", M_Reset, ) 52 | 53 | 54 | #if 0 55 | #append concommandslist cmd("m_servers", M_Servers, servers.qc) 56 | #define serverbrowser "m_servers" 57 | #else 58 | #define serverbrowser "menu_servers" 59 | #endif 60 | 61 | //make sure all the right files are included 62 | #define cmd(n,fnc,inc) inc 63 | #includelist 64 | concommandslist 65 | #endlist 66 | #undef cmd 67 | 68 | mitem_desktop desktop; 69 | void() m_shutdown = {}; 70 | void(vector screensize) m_draw = {items_draw(desktop);}; 71 | void(float scan, float chr) m_keydown = {items_keypress(desktop, scan, chr, TRUE);}; 72 | void(float scan, float chr) m_keyup = {items_keypress(desktop, scan, chr, FALSE);}; 73 | void(float mode) m_toggle 74 | { //mode is stupid. 1=enable,0=disable,-1=actually toggle. 75 | if (mode < 0) 76 | mode = !desktop.item_kactivechild; 77 | if (mode) 78 | M_Main(desktop); 79 | else while(desktop.item_kactivechild) 80 | { 81 | mitem it = desktop.item_kactivechild; 82 | if (it.item_flags & IF_NOKILL) 83 | break; 84 | it.item_remove(); 85 | } 86 | 87 | items_updategrabs(TRUE); 88 | }; 89 | 90 | var float autocvar_dp_workarounds_allow = TRUE; 91 | var float autocvar_dp_workarounds_force = FALSE; 92 | void() m_init = 93 | { 94 | desktop = spawn(mitem_desktop); 95 | 96 | //register the console commands via the alias command. 97 | #define cmd(n,f) localcmd("alias " n " \"menu_cmd " n " $*\"\n"); 98 | concommandslist 99 | #undef cmd 100 | 101 | //work around some dp differences/bugs. 102 | //this check identifies one significant bug in DP. 103 | //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. 104 | if (checkextension("DP_QC_RENDER_SCENE") && !checkextension("DP_CON_SET")) 105 | dp_workarounds = autocvar(dp_workarounds_allow, TRUE); 106 | if (autocvar(dp_workarounds_force, FALSE)) 107 | dp_workarounds = TRUE; 108 | 109 | if (dp_workarounds) 110 | print("^1WORKING AROUND DP BUGS\n"); 111 | 112 | //for compat with DP, 'none' is the default cursor in menuqc. 113 | //naturally this is not ideal. 114 | if (checkextension("FTE_QC_HARDWARECURSORS")) 115 | setcursormode(TRUE, ""); 116 | else 117 | print("No hardware cursors\n"); 118 | 119 | if (clientstate() == 1) //disconnected==1, supposedly 120 | m_toggle(1); 121 | }; 122 | void(string cstr) GameCommand = 123 | { 124 | tokenize(cstr); 125 | string cmd = argv(0); 126 | 127 | switch(cmd) 128 | { 129 | //switch on the known commands. 130 | #define cmd(n,f) case n: f(desktop); break; 131 | concommandslist 132 | #undef cmd 133 | default: 134 | print("unknown command ", cmd, "\n"); 135 | break; 136 | } 137 | items_updategrabs(TRUE); 138 | }; 139 | -------------------------------------------------------------------------------- /menu/options.qc: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | Options menu. 3 | just a simple list. 4 | */ 5 | nonstatic void(mitem_desktop desktop) M_Options = 6 | { 7 | local float pos; 8 | mitem_exmenu m; 9 | m = spawn(mitem_exmenu, item_text:_("Options"), item_flags:IF_SELECTABLE, item_command:"m_main"); 10 | 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'); 11 | desktop.item_focuschange(m, IF_KFOCUSED); 12 | m.totop(); 13 | 14 | /* //center the actual items 15 | pos = (16/-2)*(9); 16 | 17 | //draw title art above the options 18 | mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); 19 | m.addm(banner, [(-160-banner.item_size_x)*0.5, pos-32], [(-160+banner.item_size_x)*0.5, pos-8]); 20 | */ 21 | 22 | float h = 200 * 0.5; 23 | //draw title art above the options 24 | mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); 25 | 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]); 26 | 27 | //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. 28 | mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); 29 | 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]); 30 | 31 | float fl = RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN; 32 | pos = 0; 33 | 34 | 35 | 36 | //and show the options. 37 | /* if (assumefalsecheckcommand("fps_preset")) */ 38 | /* {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;} */ 39 | 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; 40 | 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; 41 | 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; 42 | 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; 43 | 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; 44 | if (assumefalsecheckcommand("cfg_save")) 45 | {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;} 46 | if (assumefalsecheckcommand("cvarreset")) 47 | {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;} 48 | 49 | //random art for style 50 | #if 1//def CSQC 51 | //m.addm(spawn (mitem_spinnymodel, item_text: "progs/suit.mdl"), [0, 12*-16/2], [160, 12*16/2]); 52 | m.add(spawn ( 53 | mitem_spinnymodel, 54 | item_text: "progs/tf_stan.mdl", 55 | dontrotate:0, 56 | startangle:'0 192 0', 57 | firstframe:0, 58 | framecount:1, 59 | fov:40 60 | ), 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]); 61 | #else 62 | //menuqc doesn't support entities. shove some random crappy static image there instead. 63 | local mitem_pic plaque = spawn (mitem_pic, item_text:"gfx/qplaque.lmp", item_alpha:1); 64 | m.addm(plaque, [(160-plaque.item_size_x)*0.5, 12*-16/2], [(160+plaque.item_size_x)*0.5, 12*16/2]); 65 | #endif 66 | addmenuback(m); 67 | }; 68 | 69 | 70 | 71 | static void(mitem_desktop desktop, string question, string affirmitive, string affirmitiveaction, string negative, string negativeaction) M_SimplePrompt = 72 | { 73 | local float pos; 74 | mitem_exmenu m; 75 | m = spawn(mitem_exmenu, item_text:_("Options"), item_flags:IF_SELECTABLE, item_command:""); 76 | 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'); 77 | desktop.item_exclusive = m; 78 | desktop.item_focuschange(m, IF_KFOCUSED); 79 | m.totop(); 80 | 81 | //center the actual items 82 | pos = (16/-2)*(2); 83 | 84 | //draw title art above the options 85 | // mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); 86 | // 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]); 87 | 88 | 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; 89 | 90 | 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; 91 | 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; 92 | 93 | //random art for style 94 | #if 1//def CSQC 95 | 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]); 96 | #else 97 | //menuqc doesn't support entities. shove some random crappy static image there instead. 98 | local mitem_pic plaque = spawn (mitem_pic, item_text:"gfx/qplaque.lmp", item_alpha:1); 99 | m.addm(plaque, [(160-plaque.item_size_x)*0.5, 12*-16/2], [(160+plaque.item_size_x)*0.5, 12*16/2]); 100 | #endif 101 | addmenuback(m); 102 | }; 103 | 104 | nonstatic void(mitem_desktop desktop) M_Reset = 105 | { 106 | M_SimplePrompt(desktop, "Really Reset All Settings?", "Yes!", "m_pop;cvarreset *;exec default.cfg", "NOOOO! MY PRECIOUS!!!", "m_pop"); 107 | }; 108 | -------------------------------------------------------------------------------- /menu/options_audio.qc: -------------------------------------------------------------------------------- 1 | nonstatic void(mitem_desktop desktop) M_Options_Audio = 2 | { 3 | local float pos; 4 | mitem_exmenu m; 5 | m = spawn(mitem_exmenu, item_text:_("Audio Options"), item_flags:IF_SELECTABLE, item_command:"m_options"); 6 | 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'); 7 | desktop.item_focuschange(m, IF_KFOCUSED); 8 | m.totop(); 9 | 10 | float h = 200 * 0.5; 11 | //draw title art above the options 12 | mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); 13 | 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]); 14 | 15 | //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. 16 | mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); 17 | 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]); 18 | 19 | float fl = RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN; 20 | pos = 0; 21 | 22 | //add the options 23 | 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; 24 | pos += 8; 25 | fr.add(menuitemcombo_spawn(_("Sound Device"), "s_device", '280 8', cvar_string("_s_device_opts")), fl, [0, pos], [0, 8]); pos += 8; 26 | fr.add(menuitemslider_spawn(_("Master Volume"), "volume", '0.0 1 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; 27 | fr.add(menuitemslider_spawn(_("Jump Volume"), "fo_jumpvolume", '0.0 1 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; 28 | fr.add(menuitemslider_spawn(_("Reload Volume"), "fo_reloadvolume", '0.0 1 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; 29 | fr.add(menuitemslider_spawn(_("Ambient Volume"),"ambient_level", '0 0.5 0.05', '280 8'), fl, [0, pos], [0, 8]); pos += 8; 30 | fr.add(menuitemslider_spawn(_("Self Volume"), "s_localvolume", '0 1 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; 31 | fr.add(menuitemslider_spawn(_("Music Volume"), "bgmvolume", '0 0.5 0.05', '280 8'), fl, [0, pos], [0, 8]); pos += 8; 32 | fr.add(menuitemslider_spawn(_("Channels"), "s_numspeakers", '1 6 1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; 33 | fr.add(menuitemcombo_spawn(_("Audio Quality"), "s_khz", '280 8', _( 34 | "11025 \"11025hz (vanilla quake)\" " 35 | "22050 \"22050hz\" " 36 | "44100 \"44100hz (cd quality)\" " 37 | "48000 \"48000hz (dvd quality)\" " 38 | "96000 \"96000hz\" " 39 | "192000 \"192000hz\" " 40 | )), fl, [0, pos], [0, 8]); pos += 8; 41 | fr.add(menuitemcheck_spawn(_("Doppler"), "s_doppler", '280 8'), fl, [0, pos], [0, 8]); pos += 8; 42 | fr.add(menuitemcheck_spawn(_("8bit audio"), "s_loadas8bit", '280 8'), fl, [0, pos], [0, 8]); pos += 8; 43 | fr.add(menuitemcheck_spawn(_("Swap Speakers"), "s_swapstereo", '280 8'), fl, [0, pos], [0, 8]); pos += 8; 44 | fr.add(menuitemslider_spawn(_("Latency"), "s_mixahead", '0.1 0.3 0.01', '280 8'), fl, [0, pos], [0, 8]); pos += 8; 45 | fr.add(menuitemcheck_spawn(_("Disable Sound"), "nosound", '280 8'), fl, [0, pos], [0, 8]); pos += 8; 46 | //ambient fade 47 | fr.add(menuitemcheck_spawn(_("Static Sounds"), "cl_staticsounds", '280 8'), fl, [0, pos], [0, 8]); pos += 8; 48 | fr.add(menuitemcheck_spawn(_("Mix in Background"),"s_inactive", '280 8'), fl, [0, pos], [0, 8]); pos += 8; 49 | 50 | pos += 8; 51 | fr.add(menuitemcombo_spawn(_("Microphone Device"), "cl_voip_capturedevice", '280 8', cvar_string("_cl_voip_capturedevice_opts")), 52 | fl, [0, pos], [0, 8]); pos += 8; 53 | fr.add(menuitemslider_spawn(_("VOIP Playback Vol"),"cl_voip_play", '0 2 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; 54 | fr.add(menuitemcheck_spawn(_("VOIP Test"), "cl_voip_test", '280 8'), fl, [0, pos], [0, 8]); pos += 8; 55 | fr.add(menuitemslider_spawn(_("VOIP Record Vol"), "cl_voip_micamp", '0 4 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; 56 | fr.add(menuitemcombo_spawn(_("VOIP Mode"), "cl_voip_send", '280 8', _( 57 | "0 \"Push-To-Talk\" 1 " 58 | "\"Voice Activation\" " 59 | "2 \"Continuous\"" 60 | )), fl, [0, pos], [0, 8]); pos += 8; 61 | //VAD threshhold 62 | //ducking 63 | //noise cancelation 64 | fr.add(menuitemcombo_spawn(_("VOIP Codec"), "cl_voip_codec", '280 8',_( 65 | "0 \"speex (narrow 11khz)\" " 66 | //"1 \"raw (wasteful)\" " 67 | "2 \"opus\" " 68 | "3 \"speex (narrow 8khz)\" " 69 | "4 \"speex (wide 16khz)\" " 70 | "5 \"speex (ultrawide 32khz)\" " 71 | )), fl, [0, pos], [0, 8]); pos += 8; 72 | 73 | fr.add(menuitemslider_spawn(_("Opus bitrate"), "cl_voip_bitrate", '0.5 128 0.5','280 8'), fl, [0, pos], [0, 8]); pos += 8; 74 | 75 | addmenuback(m); 76 | }; 77 | -------------------------------------------------------------------------------- /menu/options_effects.qc: -------------------------------------------------------------------------------- 1 | nonstatic void(mitem_desktop desktop) M_Options_Effects = 2 | { 3 | mitem_exmenu m; 4 | m = spawn(mitem_exmenu, item_text:_("Effects Options"), item_flags:IF_SELECTABLE, item_command:"m_options"); 5 | 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'); 6 | desktop.item_focuschange(m, IF_KFOCUSED); 7 | m.totop(); 8 | 9 | float h = 200 * 0.5; 10 | //draw title art above the options 11 | mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); 12 | 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]); 13 | //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. 14 | mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); 15 | 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]); 16 | float fl = RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN; 17 | float pos = 0; 18 | 19 | fr.add(menuitemcheck_spawn(_("Show Framerate"), dp("showfps", "show_fps"), '280 8'), fl, [0, pos], [0, 8]); pos += 8; 20 | fr.add(menuitemcheck_spawn(_("Bloom"), "r_bloom", '280 8'), fl, [0, pos], [0, 8]); pos += 8; 21 | fr.add(menuitemcheck_spawn(_("Simple Textures"), "r_drawflat", '280 8'), fl, [0, pos], [0, 8]); pos += 8; 22 | fr.add(menuitemcheck_spawn(_("Paletted Rendering"), "r_softwarebanding", '280 8'), fl, [0, pos], [0, 8]); pos += 8; 23 | fr.add(menuitemcheck_spawn(_("HDR"), "r_hdr_irisadaptation", '280 8'), fl, [0, pos], [0, 8]); pos += 8; 24 | fr.add(menuitemcheck_spawn(_("Coronas"), "r_coronas", '280 8'), fl, [0, pos], [0, 8]); pos += 8; 25 | fr.add(menuitemcheck_spawn(_("High Res Textures"), "gl_load24bit", '280 8'), fl, [0, pos], [0, 8]); pos += 8; 26 | fr.add(menuitemcheck_spawn(_("Relief Mapping"), "r_glsl_offsetmapping", '280 8'), fl, [0, pos], [0, 8]); pos += 8; 27 | fr.add(menuitemcheck_spawn(_("Realtime Dynamic Lights"), "r_shadow_realtime_dlight", '280 8'), fl, [0, pos], [0, 8]); pos += 8; 28 | fr.add(menuitemcheck_spawn(_("Realtime World Lighting"), "r_shadow_realtime_world", '280 8'), fl, [0, pos], [0, 8]); pos += 8; 29 | fr.add(menuitemcombo_spawn(_("Texture Mode"), "gl_texturemode", '280 8', _( 30 | "nll \"Blocky\" " 31 | "lll \"Blurry\" " 32 | )), fl, [0, pos], [0, 8]); pos += 8; 33 | 34 | 35 | fr.add(menuitemslider_spawn(_("Particle Density"), "r_part_density", '0.25 4 0.25', '280 8'), fl, [0, pos], [0, 8]); pos += 8; 36 | 37 | fr.add(menuitemcombo_spawn(_("Water Effects"), "r_waterstyle", '280 8', _( 38 | "1 \"Classic\" " 39 | "2 \"Ripples\" " 40 | "3 \"Reflections\" " 41 | )), fl, [0, pos], [0, 8]); pos += 8; 42 | fr.add(menuitemcombo_spawn(_("View Projection"), "r_projection", '280 8', _( 43 | "0 \"Standard\" " 44 | "1 \"Stereographic / Pannini\" " 45 | "2 \"Fish-Eye\" " 46 | "3 \"Panoramic\" " 47 | "4 \"Lambert Azimuthal Equal-Area\" " 48 | "5 \"Equirectangular\" " 49 | )), fl, [0, pos], [0, 8]); pos += 8; 50 | fr.add(menuitemcombo_spawn(_("View Projection Fov"), "ffov", '280 8', _( 51 | "90 \"Normal\" " 52 | "180 \"180 degrees\" " 53 | "270 \"270 degrees\" " 54 | "360 \"360 degrees\" " 55 | )), fl, [0, pos], [0, 8]); pos += 8; 56 | 57 | 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; 58 | 59 | addmenuback(m); 60 | }; 61 | 62 | -------------------------------------------------------------------------------- /menu/options_keys.qc: -------------------------------------------------------------------------------- 1 | const static struct 2 | { 3 | string name; 4 | string cmd; 5 | } binds[] = 6 | { 7 | {_(""), "Movement"}, 8 | {0, 0}, 9 | {_("Move Forward"), "+forward"}, 10 | {_("Move Back"), "+back"}, 11 | {_("Move Left"), "+moveleft"}, 12 | {_("Move Right"), "+moveright"}, 13 | {_("Jump"), "+jump"}, 14 | {_("Swim Up"), "+moveup"}, 15 | {_("Swim Down"), "+movedown"}, 16 | /* {_("Turn Left"), "+left"}, */ 17 | /* {_("Turn Right"), "+right"}, */ 18 | /* {_("Look Up"), "+lookup"}, */ 19 | /* {_("Look Down"), "+lookdown"}, */ 20 | /* {_("Center view"), "centerview"}, */ 21 | {0, 0}, 22 | {0, 0}, 23 | {_(""), "Actions"}, 24 | {0, 0}, 25 | {_("Fire"), "+attack"}, 26 | {_("Class Special"), "special"}, 27 | {_("Class Menu"), "fo_menu_special"}, 28 | {_("Grenade 1"), "gren1"}, 29 | {_("Grenade 2"), "gren2"}, 30 | {_("Reload"), "reload"}, 31 | {_("Reload Next"), "reloadnext"}, 32 | {_("Change Class"), "changeclass"}, 33 | {_("Change Team"), "changeteam"}, 34 | {_("Drop Flag"), "dropflag"}, 35 | {_("Discard Backpack"), "discard"}, 36 | {_("Discard Ammo"), "dropammo"}, 37 | {_("Call For Help"), "saveme"}, 38 | {0, 0}, 39 | {0, 0}, 40 | {_(""), "Weapon Select"}, 41 | {0, 0}, 42 | {_("Next Weapon"), "weapnext"}, 43 | {_("Prev Weapon"), "weapprev"}, 44 | {_("Last Weapon"), "weaplast"}, 45 | {_("Primary Weapon"), "impulse 1"}, 46 | {_("Secondary Weapon"), "impulse 2"}, 47 | {_("Tertiary Weapon"), "impulse 3"}, 48 | {_("Melee"), "impulse 4"}, 49 | {0, 0}, 50 | {0, 0}, 51 | {_(""), "Advanced Weaponry"}, 52 | {0, 0}, 53 | {_("Fire Primary"), "+quick1"}, 54 | {_("Fire Secondary"), "+quick2"}, 55 | {_("Fire Tertiary"), "+quick3"}, 56 | {_("Fire Melee"), "+quick4"}, 57 | {_("Quick Primary"), "+slot1"}, 58 | {_("Quick Secondary"), "+slot2"}, 59 | {_("Quick Tertiary"), "+slot3"}, 60 | {_("Quick Melee"), "+slot4"}, 61 | {_("Prime Grenade 1"), "primeone"}, 62 | {_("Prime Grenade 2"), "primetwo"}, 63 | {_("Throw Grenade"), "throwgren"}, 64 | /* {_("Inventory"), "inv"}, */ 65 | /* {_("Flag Status"), "flaginfo"}, */ 66 | {0, 0}, 67 | {0, 0}, 68 | {_(""), "Misc"}, 69 | {0, 0}, 70 | {_("Scores"), "+fo_showscores"}, 71 | {_("Ready Up"), "ready"}, 72 | {_("Server Chat"), "messagemode"}, 73 | {_("Team Chat"), "messagemode2"}, 74 | {_("Map Help"), "maphelp"}, 75 | {_("Main Menu"), "togglemenu"}, 76 | /* {_("Break Match"), "break"}, */ 77 | /* {_("Voice Chat"), "+voip"}, */ 78 | // {_("Mouse Look"), "+mlook"}, 79 | // {_("Keyboard Look"), "+klook"}, 80 | /* {_("Strafe"), "+strafe"}, */ 81 | /* {_("Run"), "+speed"}, */ 82 | /* {_("Spectate"), "observe"}, */ 83 | /* {_("Join Match"), "join"}, */ 84 | }; 85 | void(mitem_desktop desktop) M_Options_Keys = 86 | { 87 | float i; 88 | float h; 89 | 90 | //create the menu, give it focus, and make sure its displayed over everything else. 91 | mitem_exmenu m = spawn(mitem_exmenu, item_text:_("Key Options"), item_flags:IF_SELECTABLE, item_command:"m_options"); 92 | 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'); 93 | desktop.item_focuschange(m, IF_KFOCUSED); 94 | m.totop(); 95 | 96 | //figure out the size of the stuff 97 | // h = sizeof(binds) / sizeof(binds[0]); 98 | // h *= 8; 99 | h = 200; 100 | h *= 0.5; //and halve it 101 | 102 | //draw title art above the options 103 | mitem_pic banner = spawn(mitem_pic, item_text:"gfx/ttl_cstm.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); 104 | 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]); 105 | 106 | //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. 107 | mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); 108 | 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]); 109 | 110 | float f = fopen("bindlist.lst", FILE_READ); 111 | if (f >= 0) 112 | { 113 | //throw a load of bind options onto it by reading from the array. 114 | for (i = 0; ; ) 115 | { 116 | string line = fgets(f); 117 | if not (line) 118 | break; //eof 119 | float args = tokenize(line); 120 | if (!args) 121 | continue; //blank line 122 | string c = argv(0); 123 | string n = argv(1); 124 | string t = argv(2); 125 | if (c == "-") //command only 126 | { 127 | if (n != "") 128 | { 129 | mitem it = menuitemtext_spawn(n, "", 8); 130 | it.item_flags &= ~IF_SELECTABLE; 131 | 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); 132 | } 133 | } 134 | else 135 | 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'); 136 | i += 8; 137 | } 138 | fclose(f); 139 | } 140 | else 141 | { 142 | //throw a load of bind options onto it by reading from the array. 143 | for (i = 0; i < sizeof(binds) / sizeof(binds[0]); i++) 144 | { 145 | if (binds[i].name == "") { //no name is a spacer 146 | if(binds[i].cmd) { 147 | 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]); 148 | i ++; 149 | } 150 | continue; 151 | } 152 | 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'); 153 | } 154 | } 155 | 156 | //and give us a suitable menu tint too, just because. 157 | addmenuback(m); 158 | }; 159 | -------------------------------------------------------------------------------- /menu/options_video.qc: -------------------------------------------------------------------------------- 1 | nonstatic void(mitem_desktop desktop) M_Options_Video = 2 | { 3 | mitem_exmenu m; 4 | m = spawn(mitem_exmenu, item_text:_("Video Options"), item_flags:IF_SELECTABLE, item_command:"m_options"); 5 | 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'); 6 | desktop.item_focuschange(m, IF_KFOCUSED); 7 | m.totop(); 8 | 9 | float h = 200 * 0.5; 10 | //draw title art above the options 11 | mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); 12 | 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]); 13 | //spawn a container frame for the actual options. this provides a scrollbar if we have too many items. 14 | mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE); 15 | 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]); 16 | float fl = RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_OWN_MIN; 17 | float pos = 0; 18 | 19 | pos += 8; 20 | 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; 21 | pos += 8; 22 | 23 | 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; 24 | 25 | //add the options 26 | if (!dp_workarounds) 27 | { 28 | fr.add(menuitemcombo_spawn(_("Display Mode"), "vid_fullscreen", '280 8', 29 | "0 \"Windowed\" " 30 | "1 \"Fullscreen\" " 31 | "2 \"Borderless Windowed\" " 32 | ), fl, [0, pos], [0, 8]); pos += 8; 33 | } 34 | else 35 | { 36 | fr.add(menuitemcheck_spawn(_("Fullscreen"), "vid_fullscreen", '280 8'), fl, [0, pos], [0, 8]); pos += 8; 37 | } 38 | if (cvar_type("vid_resizable")) fr.add(menuitemcheck_spawn(_("Resizable"), "vid_resizable", '280 8'), fl, [0, pos], [0, 8]); pos += 8; 39 | fr.add(menuitemcombo_spawn(_("Anti-Aliasing"), dp("vid_samples", "vid_multisample"), '280 8', 40 | "0 \"Off\" " 41 | "2 \"2x\" " 42 | "4 \"4x\" " 43 | ), fl, [0, pos], [0, 8]); pos += 8; 44 | 45 | //as far as video mode selections go, this is shite. 46 | //should probably have an aspect+modes option instead, but that makes the combo really messy. especially as that would be two cvars. 47 | fr.add(menuitemcombo_spawn(_("Video Width"), "vid_width", '280 8', _( 48 | "0 \"Default\" " 49 | "640 \"640\" " 50 | "800 \"800\" " 51 | "1024 \"1024\" " 52 | "1280 \"1280\" " 53 | "1920 \"1920\" " 54 | )), fl, [0, pos], [0, 8]); pos += 8; 55 | fr.add(menuitemcombo_spawn(_("Video Height"), "vid_height", '280 8', _( 56 | "0 \"Default\" " 57 | "480 \"480\" " 58 | "600 \"600\" " 59 | "768 \"768\" " 60 | "720 \"720\" " 61 | "1080 \"1080\" " 62 | )), fl, [0, pos], [0, 8]); pos += 8; 63 | fr.add(menuitemcombo_spawn(_("Video Zoom"), "vid_conautoscale", '280 8', _( 64 | "0 \"Default\" " 65 | "1.5 \"x1.5\" " 66 | "2 \"x2\" " 67 | "4 \"x4\" " 68 | )), fl, [0, pos], [0, 8]); pos += 8; 69 | fr.add(menuitemcombo_spawn(_("Colour Depth"), dp("vid_bitsperpixel", "vid_bpp"), '280 8', _( 70 | "16 \"16bit\" " 71 | "32 \"24bit\" " //alpha doesn't count. 72 | )), fl, [0, pos], [0, 8]); pos += 8; 73 | fr.add(menuitemcombo_spawn(_("Refresh Rate"), "vid_displayfrequency", '280 8', _( 74 | "0 \"Default\" " 75 | // "60 \"60\" " 76 | // "75 \"75\" " 77 | )), fl, [0, pos], [0, 8]); pos += 8; 78 | fr.add(menuitemcheck_spawn(_("VSync"), dp("vid_vsync", "vid_wait"), '280 8'), fl, [0, pos], [0, 8]); pos += 8; 79 | fr.add(menuitemcheck_spawn(_("Show Framerate"), dp("showfps", "show_fps"), '280 8'), fl, [0, pos], [0, 8]); pos += 8; 80 | fr.add(menuitemslider_spawn(_("View Size"), "viewsize", '50 120 10', '280 8'), fl, [0, pos], [0, 8]); pos += 8; 81 | fr.add(menuitemslider_spawn(_("Field Of View"), "fov", '50 140 5', '280 8'), fl, [0, pos], [0, 8]); pos += 8; 82 | fr.add(menuitemslider_spawn(_("Gamma"), "gamma", '1.3 0.5 -0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; 83 | fr.add(menuitemslider_spawn(_("Contrast"), "contrast", '0.7 2 0.1', '280 8'), fl, [0, pos], [0, 8]); pos += 8; 84 | fr.add(menuitemslider_spawn(_("Brightness"), "brightness", '0 0.4 0.05', '280 8'), fl, [0, pos], [0, 8]); pos += 8; 85 | fr.add(menuitemcombo_spawn(_("Hardware Gamma"), "vid_hardwaregamma", '280 8', 86 | "0 \"Off\" " 87 | "1 \"Auto\" " 88 | "2 \"Soft\" " 89 | "3 \"Hard\" " 90 | "4 \"Scene Only\" " 91 | ), fl, [0, pos], [0, 8]); pos += 8; 92 | 93 | addmenuback(m); 94 | }; 95 | 96 | -------------------------------------------------------------------------------- /menu/presets.qc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FortressOne/server-qwprogs/33dcdde328c41b24040fd347aa7ba07c32e87319/menu/presets.qc -------------------------------------------------------------------------------- /menu/quit.qc: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | Quit menu. Quite lame for now. 3 | */ 4 | float() cvars_haveunsaved = #0; 5 | nonstatic void(mitem_desktop desktop) M_Quit = 6 | { 7 | local float pos; 8 | mitem_exmenu m; 9 | m = spawn(mitem_exmenu, item_text:_("Options"), item_flags:IF_SELECTABLE, item_command:"m_main"); 10 | 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'); 11 | desktop.item_focuschange(m, IF_KFOCUSED); 12 | m.totop(); 13 | 14 | //center the actual items 15 | pos = (16/-2)*(2); 16 | 17 | //draw title art above the options 18 | // mitem_pic banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN); 19 | // 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]); 20 | 21 | if (cvars_haveunsaved()) 22 | { 23 | 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; 24 | 25 | 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; 26 | 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; 27 | 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; 28 | } 29 | else 30 | { 31 | 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; 32 | 33 | 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; 34 | 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; 35 | } 36 | 37 | //random art for style 38 | #if 1//def CSQC 39 | 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]); 40 | #else 41 | //menuqc doesn't support entities. shove some random crappy static image there instead. 42 | local mitem_pic plaque = spawn (mitem_pic, item_text:"gfx/qplaque.lmp", item_alpha:1); 43 | m.addm(plaque, [(160-plaque.item_size_x)*0.5, 12*-16/2], [(160+plaque.item_size_x)*0.5, 12*16/2]); 44 | #endif 45 | addmenuback(m); 46 | }; -------------------------------------------------------------------------------- /menusys/mitem_bind.qc: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | key binding item. 3 | interactable - queries binds for a command, and accepts new scan codes to bind for its given command. 4 | */ 5 | class mitem_bind : mitem 6 | { 7 | virtual void(vector pos) item_draw; 8 | virtual float(vector pos, float scan, float char, float down) item_keypress; 9 | virtual void(mitem newfocus, float flag) item_focuschange; 10 | 11 | void() mitem_bind = 12 | { 13 | item_scale = item_size[1]; 14 | 15 | item_flags |= IF_SELECTABLE; 16 | 17 | item_text = strzone(item_text); 18 | item_command = strzone(item_command); 19 | }; 20 | virtual void() item_remove = 21 | { 22 | strunzone(item_text); 23 | strunzone(item_command); 24 | }; 25 | }; 26 | #define menuitembind_spawn(text,command,sz) \ 27 | spawn(mitem_bind, \ 28 | item_text: text, \ 29 | item_command: command, \ 30 | item_size: sz \ 31 | ) 32 | 33 | void(vector pos) mitem_bind::item_draw = 34 | { 35 | /*this is not my API...*/ 36 | tokenize(findkeysforcommand(self.item_command)); 37 | string key1 = argv(0); 38 | string key2 = argv(1); 39 | if (key1 != "") key1 = (key1=="-1")?"":keynumtostring(stof(key1)); 40 | if (key2 != "") key2 = (key2=="-1")?"":keynumtostring(stof(key2)); 41 | 42 | super::item_draw(pos); 43 | pos_x += self.item_size_x / 2; 44 | 45 | if (self.item_flags & IF_INTERACT) 46 | { 47 | ui.drawstring(pos, "Please press a key", '1 1 0' * self.item_scale, menuitem_textcolour(self), self.item_alpha, 0); 48 | } 49 | else 50 | { 51 | ui.drawstring(pos, key1, '1 1 0' * self.item_scale, menuitem_textcolour(self), self.item_alpha, 0); 52 | pos_x += stringwidth(key1, TRUE, '1 1 0'*self.item_scale); 53 | 54 | if (key2 != "") 55 | { 56 | ui.drawstring(pos, " or ", '1 1 0' * self.item_scale, menuitem_textcolour(self), self.item_alpha, 0); 57 | pos_x += stringwidth(" or ", TRUE, '1 1 0'*self.item_scale); 58 | 59 | ui.drawstring(pos, key2, '1 1 0' * self.item_scale, menuitem_textcolour(self), self.item_alpha, 0); 60 | // pos_x += stringwidth(key2, TRUE, '1 1 0'*self.item_scale); 61 | } 62 | } 63 | }; 64 | float(vector pos, float scan, float char, float down) mitem_bind::item_keypress = 65 | { 66 | if (!down) 67 | return FALSE; 68 | 69 | if (self.item_flags & IF_INTERACT) 70 | { 71 | if (scan == K_ESCAPE) 72 | { 73 | } 74 | else if (scan) 75 | localcmd(sprintf("bind \"%s\" \"%s\"\n", keynumtostring(scan), self.item_command)); 76 | else 77 | return FALSE; 78 | self.item_flags -= IF_INTERACT; 79 | return TRUE; 80 | } 81 | else 82 | { 83 | if (scan == K_ENTER || (scan == K_MOUSE1 && mouseinbox(pos, self.item_size))) 84 | { 85 | self.item_flags |= IF_INTERACT; 86 | return TRUE; 87 | } 88 | if (scan == K_DEL || scan == K_BACKSPACE) 89 | { 90 | /*again, this is not my API...*/ 91 | tokenize(findkeysforcommand(self.item_command)); 92 | string key1 = argv(0); 93 | string key2 = argv(1); 94 | if (key1 != "") key1 = (key1=="-1")?"":keynumtostring(stof(key1)); 95 | if (key2 != "") key2 = (key2=="-1")?"":keynumtostring(stof(key2)); 96 | if (key1 != "") localcmd(sprintf("bind \"%s\" \"\"\n", key1)); 97 | if (key2 != "") localcmd(sprintf("bind \"%s\" \"\"\n", key2)); 98 | return TRUE; 99 | } 100 | return FALSE; 101 | } 102 | }; 103 | void(mitem newfocus, float flag) mitem_bind::item_focuschange = 104 | { 105 | if (!(self.item_flags & IF_KFOCUSED)) 106 | self.item_flags = self.item_flags - (self.item_flags & IF_INTERACT); 107 | }; 108 | 109 | -------------------------------------------------------------------------------- /menusys/mitem_checkbox.qc: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | checkbox, directly linked to a cvar. 3 | */ 4 | class mitem_check : mitem 5 | { 6 | virtual float(vector pos, float scan, float char, float down) item_keypress = 7 | { 8 | if (!down) 9 | return FALSE; 10 | 11 | if (scan == K_ENTER || scan == K_SPACE || scan == K_LEFTARROW || scan == K_RIGHTARROW || scan == K_MOUSE1) 12 | { 13 | pos_x += this.item_size_x / 2; 14 | // if (ui.mousepos[0] > pos_x || scan != K_MOUSE1) //don't do anything if they clicked the bit on the left to select it 15 | set(item_command, ftos(!stof(get(item_command)))); 16 | return TRUE; 17 | } 18 | else if (scan == K_DEL && down && cvar_type(item_command)) 19 | set(item_command, cvar_defstring(item_command)); 20 | return FALSE; 21 | }; 22 | virtual void(vector pos) item_draw = 23 | { 24 | vector rgb = item_rgb; 25 | if (!(item_flags & IF_SELECTABLE)) 26 | rgb *= 0.2; 27 | local float truth = stof(get(item_command)); 28 | 29 | super::item_draw(pos); 30 | pos_x += item_size_x / 2; 31 | 32 | if (dp_workarounds) 33 | { //lame, but whatever 34 | ui.drawstring(pos, chr2str(0xe080, 0xe082), '1 1 0' * this.item_scale, rgb, this.item_alpha, 0); 35 | if (truth) 36 | ui.drawstring(pos + '4 0', chr2str(0xe083), '1 1 0' * this.item_scale, rgb, this.item_alpha, 0); 37 | } 38 | else 39 | { 40 | ui.drawstring(pos, "^{e080}^{e082}", '1 1 0' * this.item_scale, rgb, this.item_alpha, 0); 41 | if (truth) 42 | ui.drawstring(pos + '4 0', "^{e083}", '1 1 0' * this.item_scale, rgb, this.item_alpha, 0); 43 | } 44 | }; 45 | void() mitem_check = 46 | { 47 | if (!item_scale) 48 | item_scale = 8; 49 | if (!item_size_y) 50 | item_size_y = item_scale; 51 | if (isvalid(item_command)) 52 | item_flags |= IF_SELECTABLE; 53 | }; 54 | 55 | virtual void() item_resized = 56 | { 57 | if (isvalid(item_command)) 58 | item_flags |= IF_SELECTABLE; 59 | else 60 | item_flags &= ~IF_SELECTABLE; 61 | super::item_resized(); 62 | }; 63 | }; 64 | 65 | //optional, can spawn direcly 66 | mitem_check(string text, string command, vector sz) menuitemcheck_spawn = 67 | { 68 | return spawn(mitem_check, item_scale: sz_y, item_text: text, item_size: sz, item_command: command); 69 | }; 70 | -------------------------------------------------------------------------------- /menusys/mitem_colours.qc: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | hue selection thing, directly linked to a cvar. 3 | We hard code 1 in the saturation+value arguments for the selection 4 | 5 | Screw Quake colours, they're too brown! 6 | */ 7 | class mitem_colours : mitem 8 | { 9 | virtual void(vector pos) item_draw; 10 | virtual float(vector pos, float scan, float char, float down) item_keypress; 11 | 12 | virtual void() item_resized = 13 | { 14 | if (isvalid(item_command)) 15 | item_flags |= IF_SELECTABLE; 16 | else 17 | item_flags &= ~IF_SELECTABLE; 18 | super::item_resized(); 19 | }; 20 | }; 21 | 22 | //http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c 23 | static vector(vector rgb) rgbtohsv = 24 | { 25 | float r = rgb_x, g = rgb_y, b = rgb_z; 26 | float maxc = max(r, g, b), minc = min(r, g, b); 27 | float h, s, l = (maxc + minc) / 2; 28 | 29 | local float d = maxc - minc; 30 | if (maxc) 31 | s = d / maxc; 32 | else 33 | s = 0; 34 | 35 | if(maxc == minc) 36 | { 37 | h = 0; // achromatic 38 | } 39 | else 40 | { 41 | if (maxc == r) 42 | h = (g - b) / d + ((g < b) ? 6 : 0); 43 | else if (maxc == g) 44 | h = (b - r) / d + 2; 45 | else 46 | h = (r - g) / d + 4; 47 | h /= 6; 48 | } 49 | 50 | return [h, s, l]; 51 | }; 52 | 53 | //http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c 54 | static vector(vector hsv) hsvtorgb = 55 | { 56 | float h = hsv_x, s = hsv_y, v = hsv_z; 57 | float r=0, g=1, b=0; 58 | 59 | while(h < 0) 60 | h+=1; 61 | while(h >= 1) 62 | h-=1; 63 | 64 | float i = floor(h * 6); 65 | float f = h * 6 - i; 66 | float p = v * (1 - s); 67 | float q = v * (1 - f * s); 68 | float t = v * (1 - (1 - f) * s); 69 | switch(i) 70 | { 71 | case 0: r = v, g = t, b = p; break; 72 | case 1: r = q, g = v, b = p; break; 73 | case 2: r = p, g = v, b = t; break; 74 | case 3: r = p, g = q, b = v; break; 75 | case 4: r = t, g = p, b = v; break; 76 | case 5: r = v, g = p, b = q; break; 77 | } 78 | 79 | return [r, g, b]; 80 | }; 81 | 82 | float(float chr) nibbletofloat = 83 | { 84 | chr = chr&0xff; 85 | if (chr >= '0' && chr <= '9') 86 | return chr - '0'; 87 | if (chr >= 'a' && chr <= 'f') 88 | return chr - 'a' + 10; 89 | if (chr >= 'A' && chr <= 'F') 90 | return chr - 'A' + 10; 91 | return 0; 92 | }; 93 | 94 | static vector(string v) hextorgb = 95 | { 96 | if (!strncmp(v, "0x", 2)) 97 | { 98 | vector r; 99 | r_x = (nibbletofloat(str2chr(v, 2))*16 + nibbletofloat(str2chr(v, 3)))/255; 100 | r_y = (nibbletofloat(str2chr(v, 4))*16 + nibbletofloat(str2chr(v, 5)))/255; 101 | r_z = (nibbletofloat(str2chr(v, 6))*16 + nibbletofloat(str2chr(v, 7)))/255; 102 | return r; 103 | } 104 | else 105 | { 106 | float legacycolour = stof(v); 107 | switch(legacycolour) 108 | { 109 | case 0: return [0xeb, 0xeb, 0xeb]/255; 110 | case 1: return [0x8f, 0x6f, 0x23]/255; 111 | case 2: return [0x8b, 0x8b, 0xcb]/255; 112 | case 3: return [0x6b, 0x6b, 0x0f]/255; 113 | case 4: return [0x7f, 0x00, 0x00]/255; 114 | case 5: return [0xaf, 0x67, 0x23]/255; 115 | case 6: return [0xff, 0xf3, 0x1b]/255; 116 | case 7: return [0xe3, 0xb3, 0x97]/255; 117 | 118 | case 8: return [0xab, 0x8b, 0xa3]/255; 119 | case 9: return [0xbb, 0x73, 0x97]/255; 120 | case 10: return [0xdb, 0xc3, 0xbb]/255; 121 | case 11: return [0x6f, 0x83, 0x7b]/255; 122 | case 12: return [0xff, 0xf3, 0x1b]/255; 123 | case 13: return [0x00, 0x00, 0xff]/255; 124 | //14+15 are fullbrights, so not valid. 125 | 126 | default: 127 | return '0 0 0'; 128 | } 129 | } 130 | }; 131 | static string(vector v) rgbtohex = 132 | { 133 | v *= 255; 134 | return sprintf("0x%02x%02x%02x", v_x, v_y, v_z); 135 | }; 136 | 137 | void(vector pos) mitem_colours::item_draw = 138 | { 139 | local float step; 140 | local float stride; 141 | local string curval; 142 | local vector rgb; 143 | 144 | super::item_draw(pos); 145 | 146 | //calculate the rgb from hue at each step across the colour block 147 | #define STEPS 32 148 | pos_x += item_size_x / 2; 149 | 150 | if (ui.mgrabs == this) 151 | { 152 | float frac; 153 | //if we're sliding it, update the value 154 | frac = (ui.mousepos[1] - pos_x-(item_size_y+4)) / (item_size_x / 2 - (item_size_y+4)); 155 | if (frac >= 0 && frac <= 1) 156 | { 157 | set(item_command, rgbtohex(hsvtorgb([frac, 1, 1]))); 158 | } 159 | } 160 | curval = get(item_command); 161 | 162 | stride = (item_size_x / 2 - (item_size_y+4)) / STEPS; 163 | 164 | ui.drawfill(pos, [item_size_y, item_size_y], hextorgb(curval), item_alpha, 0); 165 | pos_x += item_size_y+4; 166 | 167 | pos_y += 1; 168 | #if defined(MENU) || 1 169 | for (step = 0; step < STEPS; step += 1, pos_x += stride) 170 | { 171 | rgb = hsvtorgb([step/STEPS, 1, 1]); 172 | if (!(item_flags & IF_SELECTABLE)) 173 | rgb *= 0.2; 174 | ui.drawfill(pos, [stride, item_size_y-2], rgb, item_alpha, 0); 175 | } 176 | #else 177 | //FIXME: WTF is going on here? it comes out as black? wtf? 178 | //draw quads (we should probably not use an internal-to-engine shader here...) 179 | R_BeginPolygon("fill_opaque", 4); //outside so we can skip it for faster reuse by avoiding lookups 180 | rgb = hsvtorgb([0, 1, 1]); 181 | for (step = 0; step < STEPS;) 182 | { 183 | R_PolygonVertex([pos_x, pos_y+item_size_y-2], '0 1', rgb, item_alpha); 184 | R_PolygonVertex([pos_x, pos_y], '0 0', rgb, 1); 185 | 186 | pos_x += stride; 187 | step += 1; 188 | rgb = hsvtorgb([step/STEPS, 1, 1]); 189 | 190 | R_PolygonVertex([pos_x, pos_y], '1 0', rgb, 1); 191 | R_PolygonVertex([pos_x, pos_y+item_size_y-2], '1 1', rgb, item_alpha); 192 | 193 | R_EndPolygon(); 194 | } 195 | #endif 196 | #undef STEPS 197 | }; 198 | 199 | float(vector pos, float scan, float char, float down) mitem_colours::item_keypress = 200 | { 201 | if (!down) 202 | { 203 | if (ui.mgrabs == this) 204 | ui.mgrabs = __NULL__; 205 | return FALSE; 206 | } 207 | local float curval = rgbtohsv(hextorgb(get(item_command)))[0]; 208 | if (scan == K_MWHEELUP || scan == K_MWHEELDOWN) 209 | { 210 | if (mouseinbox(pos, item_size)) 211 | scan = ((scan == K_MWHEELDOWN)?K_LEFTARROW:K_RIGHTARROW); 212 | } 213 | if (scan == K_MOUSE1) 214 | { 215 | pos_x += item_size_x / 2; 216 | pos_x += item_size_y+4; 217 | curval = (ui.mousepos[0] - pos_x) / (item_size_x / 2 - item_size_y+4); 218 | if (curval < 0 || curval > 1) 219 | return FALSE; 220 | curval = curval; 221 | set(item_command, rgbtohex(hsvtorgb([(curval), 1, 1]))); 222 | ui.mgrabs = this; 223 | } 224 | else if (scan == K_LEFTARROW || scan == K_SPACE) 225 | { 226 | set(item_command, rgbtohex(hsvtorgb([curval - (1/64.0), 1, 1]))); //yay autorepeat 227 | } 228 | else if (scan == K_RIGHTARROW || scan == K_ENTER) 229 | { 230 | set(item_command, rgbtohex(hsvtorgb([curval + (1/64.0), 1, 1]))); 231 | } 232 | else 233 | return FALSE; 234 | return TRUE; 235 | }; 236 | mitem_colours(string text, string command, vector sz) menuitemcolour_spawn = 237 | { 238 | mitem_colours n = spawn(mitem_colours); 239 | n.item_scale = sz_y; 240 | n.item_text = text; 241 | n.item_size = sz; 242 | 243 | n.item_command = command; 244 | n.item_flags |= IF_SELECTABLE; 245 | return n; 246 | }; 247 | 248 | -------------------------------------------------------------------------------- /menusys/mitem_console.qc: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | Embed a (sub) console in a menu item thing. 3 | 4 | you can print to these consoles with con_print("consolename", "text to be printed\n"); 5 | they can be detached from the system console with con_getset("consolename", "hidden", "1"). be sure to getset(con, "close", "1") or unhide afterwards. 6 | 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. 7 | */ 8 | 9 | class mitem_console : mitem 10 | { 11 | virtual void() item_remove = 12 | { 13 | strunzone(item_command); 14 | super::item_remove(); 15 | }; 16 | virtual float(vector pos, float scan, float char, float down) item_keypress = 17 | { 18 | local float ret; 19 | if (scan == K_ESCAPE) //let the owning menu take it. 20 | return FALSE; 21 | if (down) 22 | { 23 | ret = con_input(item_command, IE_KEYDOWN, scan, char, 0); 24 | if (scan == K_MOUSE1 || scan == K_MOUSE2) //grab mouse, so we still receive mouse up events. 25 | { 26 | ui.mgrabs = this; 27 | ret = TRUE; 28 | local mitem_frame p = item_parent; 29 | p.item_focuschange(this, IF_KFOCUSED); 30 | } 31 | } 32 | else 33 | { 34 | ret = con_input(item_command, IE_KEYUP, scan, char, 0); //note the engine never tries to cancel key up events anyway. 35 | if (ui.mgrabs == this) 36 | ui.mgrabs = __NULL__; 37 | } 38 | return ret; 39 | }; 40 | virtual void(mitem newfocus, float flag) item_focuschange = 41 | { 42 | con_input(item_command, IE_FOCUS, !!(item_flags&IF_MFOCUSED), !!(item_flags&IF_KFOCUSED), 0); 43 | }; 44 | virtual void(vector pos) item_draw = 45 | { 46 | con_input(item_command, IE_MOUSEABS, ui.mousepos[0] - pos_x, ui.mousepos[1] - pos_y, 0); 47 | con_draw(item_command, pos, item_size, 12); //use a 12-point font, if we can. 48 | }; 49 | void() mitem_console = 50 | { 51 | item_flags |= IF_SELECTABLE; 52 | item_command = strzone(item_command); 53 | }; 54 | }; 55 | 56 | #define menuitemconsole_spawn(conname) spawn(mitem_console, item_command:conname) 57 | 58 | 59 | -------------------------------------------------------------------------------- /menusys/mitem_edittext.qc: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | editable text, directly linked to a cvar. 3 | FIXME: This can only edit the end of the string. 4 | */ 5 | class mitem_edit : mitem 6 | { 7 | virtual void(vector pos) item_draw; 8 | virtual float(vector pos, float scan, float char, float down) item_keypress; 9 | virtual void() item_remove; 10 | float spos; 11 | 12 | virtual void() item_resized = 13 | { 14 | if (isvalid(item_command)) 15 | item_flags |= IF_SELECTABLE; 16 | else 17 | item_flags &= ~IF_SELECTABLE; 18 | super::item_resized(); 19 | }; 20 | }; 21 | 22 | void() mitem_edit::item_remove = 23 | { 24 | strunzone(item_text); 25 | strunzone(item_command); 26 | super::item_remove(); 27 | }; 28 | 29 | void(vector pos) mitem_edit::item_draw = 30 | { 31 | local string curval = get(item_command); 32 | 33 | super::item_draw(pos); 34 | 35 | pos_x += item_size_x / 2; 36 | /* ui.drawfill(pos, [item_size_x/2, 1], TD_BOT, item_alpha, 0); 37 | ui.drawfill(pos, [1, self.item_size_y - 1], TD_RGT, item_alpha, 0); 38 | ui.drawfill(pos + [item_size_x/2-1, 1], [1, item_size_y - 1], TD_LFT, item_alpha, 0); 39 | ui.drawfill(pos + [0, item_size_y-1], [item_size_x/2, 1], TD_TOP, item_alpha, 0); 40 | */ pos_y += (item_size_y - item_scale)*0.5; 41 | pos_x += 1; 42 | 43 | spos = min(spos, strlen(curval)); 44 | if (((cltime*4)&1) && (item_flags & IF_KFOCUSED)) 45 | curval = strcat(substring(curval, 0, spos), chr2str(0xe00b), substring(curval, spos+1, -1)); //replace the char with a box... ugly, whatever 46 | ui.drawstring(pos, curval, '1 1 0' * item_scale, item_rgb, item_alpha, 0); 47 | }; 48 | float(vector pos, float scan, float char, float down) mitem_edit::item_keypress = 49 | { 50 | if (!down) 51 | return FALSE; 52 | 53 | local string curval = get(item_command); 54 | spos = min(spos, strlen(curval)); 55 | 56 | if (scan == K_ESCAPE) 57 | return FALSE; 58 | else if (scan == K_LEFTARROW) 59 | spos = max(spos-1, 0); 60 | else if (scan == K_RIGHTARROW) 61 | spos+=1; 62 | /* else if (scan == K_MOUSE1) 63 | { 64 | //FIXME: figure out the spos for the cursor 65 | return TRUE; 66 | }*/ 67 | else if (scan == K_BACKSPACE || scan == K_DEL) 68 | { 69 | if (spos) 70 | { 71 | curval = strcat(substring(curval, 0, spos-1), substring(curval, spos, -1)); 72 | spos -= 1; 73 | } 74 | } 75 | else if (char >= ' ') 76 | { 77 | curval = strcat(substring(curval, 0, spos), chr2str(char), substring(curval, spos, -1)); 78 | spos += strlen(chr2str(char)); 79 | } 80 | else 81 | return FALSE; 82 | 83 | set(item_command, curval); 84 | return TRUE; 85 | }; 86 | mitem_edit(string text, string command, vector sz) menuitemeditt_spawn = 87 | { 88 | mitem_edit n = spawn(mitem_edit); 89 | n.item_scale = sz_y; 90 | n.item_text = strzone(text); 91 | n.item_size = sz; 92 | n.spos = 100000; //will be clipped so meh 93 | 94 | n.item_command = strzone(command); 95 | n.item_flags |= IF_SELECTABLE; 96 | return n; 97 | }; 98 | 99 | -------------------------------------------------------------------------------- /menusys/mitem_exmenu.qc: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | fullscreen exclusive menu 3 | you should only have ONE of these visible at once. 4 | interactable - basically just a container for the items in the menu, but also handles killing them+itself when the user presses escape. 5 | will keep stealing focus from the desktop, so you won't be able to play while one of these is active. 6 | will not steal focus from siblings. this means console slideouts or whatever are still usable. 7 | 8 | these items will automatically be added to the desktop/workspace thing 9 | Call it.item_remove() to pop the menu. 10 | 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. 11 | */ 12 | 13 | class mitem_exmenu : mitem_frame 14 | { 15 | virtual float(vector pos, float scan, float char, float down) item_keypress = 16 | { 17 | local float ret = super::item_keypress(pos, scan, char, down); 18 | if (!ret && down) 19 | { 20 | ret = TRUE; 21 | if (scan == K_MOUSE2 || scan == K_ESCAPE) 22 | { 23 | localcmd(strcat(item_command, "\n")); //console command to exec if someone clicks the close button. 24 | item_remove(); 25 | } 26 | else if (scan == K_UPARROW && down) 27 | menu_selectnextitem(this, TRUE); 28 | else if (scan == K_DOWNARROW && down) 29 | menu_selectnextitem(this, FALSE); 30 | else if (scan >= K_F1 && scan <= K_F12) //allow f1-f12 to work, but every other button event gets canceled. 31 | ret = FALSE; 32 | } 33 | return ret; 34 | }; 35 | virtual void(vector pos) item_draw = 36 | { 37 | 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 38 | { 39 | if (item_flags & IF_SELECTABLE) 40 | { 41 | ui.mgrabs = __NULL__; 42 | ui.kgrabs = __NULL__; 43 | item_parent.item_focuschange(this, IF_KFOCUSED); 44 | } 45 | } 46 | super::item_draw(pos); 47 | }; 48 | 49 | //same as mitem, but does not propogate to parent. 50 | virtual string(string key) get = 51 | { 52 | return cvar_string(key); 53 | }; 54 | virtual void(string key, string newval) set = 55 | { 56 | cvar_set(key, newval); 57 | }; 58 | }; -------------------------------------------------------------------------------- /menusys/mitem_slider.qc: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | slider item, directly attached to a cvar. 3 | interactable - executes a given console command. 4 | */ 5 | class mitem_hslider : mitem 6 | { 7 | virtual void(vector pos) item_draw; 8 | virtual float(vector pos, float scan, float char, float down) item_keypress; 9 | 10 | vector item_slidercontrols; //min, max, step 11 | 12 | virtual void() item_resized = 13 | { 14 | if (isvalid(item_command)) 15 | item_flags |= IF_SELECTABLE; 16 | else 17 | item_flags &= ~IF_SELECTABLE; 18 | super::item_resized(); 19 | }; 20 | }; 21 | void(vector pos) mitem_hslider::item_draw = 22 | { 23 | local float curval; 24 | vector rgb = self.item_rgb; 25 | if (!(item_flags & IF_SELECTABLE)) 26 | rgb *= 0.2; 27 | 28 | super::item_draw(pos); 29 | pos_x += item_size_x / 2; 30 | 31 | if (ui.mgrabs == this) 32 | { 33 | //if we're sliding it, update the value 34 | curval = (ui.mousepos[0] - pos_x-8) / (10*8); 35 | curval = bound(0, curval, 1); 36 | curval = curval * (item_slidercontrols_y - item_slidercontrols_x); 37 | if (!ui.shiftheld) 38 | curval = rint(curval / item_slidercontrols_z) * item_slidercontrols_z; //round it. 39 | curval += item_slidercontrols_x; 40 | set(item_command, sprintf("%g", curval)); 41 | } 42 | curval = stof(get(item_command)); 43 | 44 | if (dp_workarounds) 45 | { //no ^U markup support. chr2str avoids warnings about non-utf8 strings which at least allows bi-compat to work. 46 | string s = strcat(chr2str(0xe080, 0xe081, 0xe081, 0xe081, 0xe081, 0xe081), chr2str(0xe081, 0xe081, 0xe081, 0xe081, 0xe081, 0xe082)); 47 | //slider background uses the fallback quake chars 48 | ui.drawstring(pos, sprintf("%s (%g)", s, curval), '1 1 0' * self.item_scale, rgb, self.item_alpha, 0); 49 | //now draw an indicater char in the right place. 50 | //the inner side of the boundary is 4 pixels wide so we can overlap the ends by that many pixels. 51 | curval = (curval - self.item_slidercontrols_x) / (self.item_slidercontrols_y - self.item_slidercontrols_x); //fractionize it 52 | curval = bound(0, curval, 1); 53 | ui.drawstring(pos + [4 + curval*10*8, 0], chr2str(0xe083), '1 1 0' * self.item_scale, rgb, self.item_alpha, 0); 54 | } 55 | else 56 | { 57 | //slider background uses the fallback quake chars 58 | 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); 59 | //now draw an indicater char in the right place. 60 | //the inner side of the boundary is 4 pixels wide so we can overlap the ends by that many pixels. 61 | curval = (curval - self.item_slidercontrols_x) / (self.item_slidercontrols_y - self.item_slidercontrols_x); //fractionize it 62 | curval = bound(0, curval, 1); 63 | ui.drawstring(pos + [4 + curval*10*8, 0], "^{e083}", '1 1 0' * self.item_scale, rgb, self.item_alpha, 0); 64 | } 65 | }; 66 | float(vector pos, float scan, float char, float down) mitem_hslider::item_keypress = 67 | { 68 | if (down&2) 69 | { 70 | //we have grabs, and mouse was released? 71 | if (scan == K_MOUSE1 && !(down&1)) 72 | { //we're done here. 73 | ui.mgrabs = __NULL__; 74 | return TRUE; 75 | } 76 | return FALSE; //not handled, don't inhibit 77 | } 78 | if (down) 79 | { 80 | local float curval = stof(get(item_command)); 81 | if (scan == K_MWHEELUP || scan == K_MWHEELDOWN) 82 | { 83 | if (ui.mousepos[0] > pos_x + item_size[0]/2) 84 | scan = ((scan == K_MWHEELDOWN)?K_LEFTARROW:K_RIGHTARROW); 85 | } 86 | if (scan == K_MOUSE1 && down) 87 | { 88 | pos_x += item_size_x / 2; 89 | if (ui.mousepos[0] < pos_x) 90 | return TRUE;//goto keyenter; 91 | curval = (ui.mousepos[0] - pos_x-8) / (10*8); 92 | if (curval < 0 || curval > 1) 93 | return FALSE; 94 | curval = curval * (item_slidercontrols_y - item_slidercontrols_x) + item_slidercontrols_x; 95 | set(item_command, sprintf("%g", curval)); 96 | ui.mgrabs = this; 97 | } 98 | else if (scan == K_DEL && down && cvar_type(item_command)) 99 | set(item_command, cvar_defstring(item_command)); 100 | else if ((scan == K_LEFTARROW || scan == K_MWHEELUP) && down) 101 | { 102 | if (item_slidercontrols_x > item_slidercontrols_y) 103 | set(item_command, sprintf("%g", min(curval - item_slidercontrols_z, item_slidercontrols_x))); 104 | else 105 | set(item_command, sprintf("%g", max(curval - item_slidercontrols_z, item_slidercontrols_x))); 106 | } 107 | else if ((scan == K_RIGHTARROW || scan == K_MWHEELDOWN) && down) 108 | { 109 | if (item_slidercontrols_x > item_slidercontrols_y) 110 | set(item_command, sprintf("%g", max(curval + item_slidercontrols_z, item_slidercontrols_y))); 111 | else 112 | set(item_command, sprintf("%g", min(curval + item_slidercontrols_z, item_slidercontrols_y))); 113 | } 114 | else if ((scan == K_ENTER || scan == K_SPACE) && down) 115 | { 116 | //keyenter: 117 | if (item_slidercontrols_x > item_slidercontrols_y) 118 | { 119 | if (curval-0.001 <= item_slidercontrols_y) 120 | set(item_command, sprintf("%g", item_slidercontrols_x)); 121 | else 122 | set(item_command, sprintf("%g", max(curval + item_slidercontrols_z, item_slidercontrols_y))); 123 | } 124 | else 125 | { 126 | if (curval+0.001 >= item_slidercontrols_y) 127 | set(item_command, sprintf("%g", item_slidercontrols_x)); 128 | else 129 | set(item_command, sprintf("%g", min(curval + item_slidercontrols_z, item_slidercontrols_y))); 130 | } 131 | } 132 | else 133 | return FALSE; 134 | return TRUE; 135 | } 136 | else if (scan == K_MOUSE1 && ui.mgrabs == this) 137 | ui.mgrabs = __NULL__; 138 | return FALSE; 139 | }; 140 | mitem_hslider(string text, string command, vector controls, vector sz) menuitemslider_spawn = 141 | { 142 | mitem_hslider n = spawn(mitem_hslider); 143 | n.item_scale = sz_y; 144 | n.item_text = text; 145 | n.item_size = sz; 146 | 147 | n.item_slidercontrols = controls; 148 | 149 | n.item_command = command; 150 | if (n.isvalid(command)) 151 | n.item_flags |= IF_SELECTABLE; 152 | return n; 153 | }; 154 | -------------------------------------------------------------------------------- /menusys/mitem_spinnymodel.qc: -------------------------------------------------------------------------------- 1 | /* 2 | renderscene stuff is always available in csqc. 3 | If the engine supports DP_QC_RENDER_SCENE then its meant to be available in menuqc too. 4 | In practise, DP advertises this to menuqc even though it has never officially supported it. 5 | At the time of writing, FTE's dev builds can do it (and only advertise the extension in builds that try to support it). 6 | 7 | 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. 8 | 9 | The mitem_spinnymodel item just shows a rotating model centered on the z axis. Simple as that. 10 | For the sake of fun gimmicks, you can specify frame information with firstframe and framecount. 11 | Additionally, you can cause it to switch to a different animation when it faces towards the camera with shootframe and shootframes. 12 | This style of animation shouldn't really be considered very complex, but might help give a small demo of the basic concept of animation. 13 | */ 14 | /* 15 | Note - DP Bugs: 16 | 1: viewport positions are interpreted in physical pixels in DP. 17 | there is no reliable way to know how many physical pixels there actually are. 18 | custom viewports are thus near unusable. 19 | 2: failure to revert the viewport to default afterwards fucks over any 2d stuff drawn afterwards. 20 | including the console. 21 | 3: bloom on worldless models draws the world. 22 | 4: lighting is applied on models even if there's no world. 23 | enabling rtlights really fucks everything up. 24 | 5: gamma/contrast/brightness are applied to the 3d view. even if there's no world. 25 | this results in horrible squares if these settings are used. 26 | 6: DP_QC_RENDER_SCENE is advertised in menuqc, but renderscene is not supported there at all. 27 | 7: avoid the use of cltime. DP doesn't support it. 28 | 29 | probably others. it really wouldn't surprise me. 30 | */ 31 | 32 | //helper function to work around a DP bug. 33 | static vector(vector v) vtodpp = 34 | { 35 | #pragma warning disable F333 36 | #ifndef CSQC_SIMPLE 37 | //so fucking disgustingly ugly. 38 | if (dp_workarounds) 39 | { 40 | v_x *= cvar("vid_width") / cvar("vid_conwidth"); 41 | v_y *= cvar("vid_height") / cvar("vid_conheight"); 42 | } 43 | #endif 44 | return v; 45 | #pragma warning enable F333 46 | }; 47 | //make sure the fields are all defined if we're in menuqc or something 48 | noref .vector origin; 49 | noref .vector angles; 50 | noref .vector mins; 51 | noref .vector maxs; 52 | noref .string model; 53 | noref .float frame, frame2, lerpfrac, renderflags; 54 | noref .string skin; 55 | float frametime; 56 | class mitem_spinnymodel : mitem 57 | { 58 | float zbias; 59 | float firstframe; 60 | float framecount; 61 | float shootframe; 62 | float shootframes; 63 | float dontrotate; 64 | float rotatespeed; 65 | vector startangle; 66 | string customskin; 67 | string trueskin; 68 | float topcolour; 69 | float bottomcolour; 70 | float fov; 71 | 72 | //angles.y = startangle; 73 | #ifndef EXT_CSQC 74 | virtual void(vector pos) item_draw = {}; 75 | #else 76 | 77 | //might as well use the class as the entity that is drawn. 78 | //it might end up clobbering any fields used for multiple things... 79 | virtual void(vector pos) item_draw = 80 | { 81 | vector orgbias; 82 | orgbias = '0 0 0'; 83 | if (dp_workarounds) 84 | 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. 85 | origin = orgbias; 86 | origin_z += zbias; 87 | 88 | // angles_y = cltime*90;//frametime*90; 89 | // angles_y += frametime*90; 90 | if(dontrotate) { 91 | angles = startangle; 92 | } else { 93 | if(!rotatespeed) 94 | rotatespeed = 90; 95 | angles_y += frametime*rotatespeed; 96 | } 97 | 98 | clearscene(); //wipe the scene, and apply the default rendering values. 99 | setviewprop(VF_MIN, vtodpp(pos)); //min pos 100 | setviewprop(VF_SIZE, vtodpp(item_size)); //size=maxpos-minpos 101 | if(!fov) 102 | fov = 30; 103 | if (dp_workarounds) 104 | 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 105 | else 106 | setviewprop(VF_AFOV, fov); //set the aproximate fov (ie: engine takes care of aspect ratio). 107 | #ifdef CSQC 108 | setproperty(VF_DRAWENGINESBAR, FALSE); 109 | setproperty(VF_DRAWWORLD, FALSE); 110 | setproperty(VF_DRAWCROSSHAIR, FALSE); 111 | #endif 112 | setviewprop(VF_ORIGIN, orgbias + '-128 0 0'); //look towards it. 113 | setviewprop(VF_ANGLES, '0 0 0'); 114 | 115 | //animate it a bit 116 | lerpfrac -= frametime*10; 117 | while(lerpfrac < 0) 118 | { 119 | lerpfrac += 1; 120 | frame2 = frame; 121 | frame += 1; 122 | if (angles_y >= 170 && shootframes && !dontrotate) 123 | { 124 | if (frame == shootframe+shootframes) 125 | { //reached the last frame. clear the shooting 'flag' and go back to idle 126 | frame = firstframe; 127 | angles_y -= 360; 128 | } 129 | else if (frame < shootframe || frame >= shootframe+shootframes) 130 | frame = shootframe; //we were still idle, apparently. 131 | } 132 | else 133 | { 134 | if (frame < firstframe || frame >= firstframe+framecount) 135 | frame = firstframe; 136 | } 137 | } 138 | 139 | addentity(this); 140 | renderscene(); 141 | if (dp_workarounds) 142 | { //dp fucks up 2d stuff if we don't explicitly restore the 3d view to fullscreen 143 | setviewprop(VF_MIN, vtodpp('0 0 0')); 144 | setviewprop(VF_SIZE, vtodpp(ui.screensize)); 145 | } 146 | setcustomskin(self, "", sprintf("q1upper \"%f\"\nq1lower \"%f\"\nqwskin \"%s\"\n", topcolour, bottomcolour, customskin)); 147 | skin = trueskin; 148 | //setcustomskin(self, skin, ""); 149 | //setcustomskin(self, "", sprintf("q1upper \"%s\"\nq1lower \"%s\"\n\n", cvar_string("topcolor"), cvar_string("bottomcolor"))); 150 | }; 151 | 152 | virtual void(vector pos) dp_draw = 153 | { 154 | }; 155 | 156 | void() mitem_spinnymodel = 157 | { 158 | #if defined(MENU) || defined(CSQC_SIMPLE) 159 | if (!checkextension("DP_QC_RENDER_SCENE") || dp_workarounds) 160 | { 161 | item_draw = dp_draw; 162 | return; 163 | } 164 | #endif 165 | precache_model(item_text); 166 | setmodel(this, item_text); //use the size information from the engine, woo for unreliability. 167 | zbias += (mins_z - maxs_z)/2 - mins_z; //center the model on its z axis, so the whole thing is visible. 168 | frame = firstframe; 169 | }; 170 | #endif 171 | }; 172 | 173 | -------------------------------------------------------------------------------- /menusys/mitem_tabs.qc: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | tabs/tab widgets. 3 | the 'tabs' widget is simply a tab-selection control. horizontal multiple choice. it draws only its currently active child. 4 | 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. 5 | */ 6 | class mitem_tabs : mitem_frame /*frame... but not really*/ 7 | { 8 | virtual void(vector pos) item_draw; 9 | virtual float(vector pos, float scan, float char, float down) item_keypress; 10 | // virtual void() item_resize; 11 | 12 | void() mitem_tabs = 13 | { 14 | item_framesize = '2 16 2'; 15 | item_flags |= IF_SELECTABLE|IF_RESIZABLE; 16 | }; 17 | }; 18 | 19 | class mitem_tab : mitem_frame 20 | { 21 | virtual float(vector pos, float scan, float char, float down) item_keypress = 22 | { 23 | if (scan == K_UPARROW && down) 24 | menu_selectnextitem(this, TRUE); 25 | else if (scan == K_DOWNARROW && down) 26 | menu_selectnextitem(this, FALSE); 27 | else if (super::item_keypress(pos, scan, char, down)) 28 | return TRUE; 29 | else 30 | return FALSE; 31 | return TRUE; 32 | }; 33 | 34 | void() mitem_tab = 35 | { 36 | item_framesize = '0 0 0'; 37 | item_flags |= IF_SELECTABLE|IF_RESIZABLE; 38 | }; 39 | }; 40 | 41 | void(vector pos) mitem_tabs::item_draw = 42 | { 43 | local mitem ch; 44 | local vector tpos = pos; 45 | local float w; 46 | local vector col; 47 | 48 | //to highlight the active tab, we draw the top line 1 pixel higher, and no bottom line 49 | for (ch = item_children; ch; ch = ch.item_next) 50 | { 51 | w = stringwidth(ch.item_text, TRUE, '8 8') + 8; 52 | 53 | ui.drawfill(tpos + '0 1', [1, 15], TD_LFT, item_alpha, 0); 54 | ui.drawfill(tpos + [w-1, 1], [1, 14], TD_RGT, item_alpha, 0); 55 | if (ch == item_kactivechild) 56 | { 57 | //top line 58 | ui.drawfill(tpos, [w, 1], TD_TOP, item_alpha, 0); 59 | } 60 | else 61 | { 62 | //top line 63 | ui.drawfill(tpos + '0 1', [w, 1], TD_TOP, item_alpha, 0); 64 | //bottom 65 | ui.drawfill(tpos + '0 15', [w, 1], TD_TOP, item_alpha, 0); 66 | } 67 | 68 | col = item_rgb; 69 | if (!(ch.item_flags & IF_SELECTABLE)) 70 | col *= 0.2; 71 | else 72 | { 73 | if (!item_kactivechild) 74 | item_focuschange(ch, IF_KFOCUSED); 75 | if (mouseinbox(tpos, [w, 16])) 76 | col_z = 0; 77 | if (ch.item_flags & IF_KFOCUSED) 78 | col_x = 0; 79 | } 80 | 81 | ui.drawstring(tpos + '4 4', ch.item_text, '8 8', col, item_alpha, 0); 82 | tpos_x += w; 83 | } 84 | ui.drawfill(tpos + '0 15', [pos_x + item_size_x - tpos_x, 1], TD_TOP, item_alpha, 0); //top 85 | ui.drawfill(pos + '0 16', [1, item_size_y - 16], TD_LFT, item_alpha, 0); //left 86 | ui.drawfill(pos + [item_size_x-1, 16], [1, item_size_y - 17], TD_RGT, item_alpha, 0); //right 87 | ui.drawfill(pos + [1, item_size_y-1], [item_size_x-1, 1], TD_BOT, item_alpha, 0); //bottom 88 | 89 | if (item_mactivechild != item_kactivechild) 90 | item_focuschange(item_kactivechild, IF_MFOCUSED); //give the tab full focus. 91 | ch = item_kactivechild; 92 | if (ch) 93 | ch.item_draw(pos + ch.item_position + [item_framesize[0], item_framesize[1]]); 94 | }; 95 | float(vector pos, float scan, float char, float down) mitem_tabs::item_keypress = 96 | { 97 | local mitem ch; 98 | local vector tpos = pos; 99 | local vector sz = '0 0 0'; 100 | local float result; 101 | 102 | if (down && (scan == K_MOUSE1 || scan == K_MOUSE2 || scan == K_MOUSE3)) 103 | { 104 | sz_y = 16; 105 | //to highlight the active tab, we draw the top line 1 pixel higher, and no bottom line 106 | for (ch = this.item_children; ch; ch = ch.item_next) 107 | { 108 | sz_x = stringwidth(ch.item_text, TRUE, '8 8') + 8; 109 | if (mouseinbox(tpos, sz)) 110 | { 111 | item_focuschange(ch, IF_KFOCUSED); //give the tab full focus. 112 | return TRUE; 113 | } 114 | 115 | tpos_x += sz_x; 116 | } 117 | } 118 | ch = item_kactivechild; 119 | if (ch) 120 | { 121 | result = ch.item_keypress(pos + [item_framesize[0], item_framesize[1]] + ch.item_position, scan, char, down); 122 | if (!result && down) 123 | { 124 | if (scan == K_TAB || scan == K_RIGHTARROW) 125 | { 126 | ch = ch.item_next; 127 | if (!ch) 128 | ch = item_children; 129 | item_focuschange(ch, IF_KFOCUSED); 130 | result = TRUE; 131 | } 132 | // else if (scan == K_LEFTARROW) 133 | // { 134 | // ch = ch.item_next; 135 | // if (!ch) 136 | // ch = item_children; 137 | // item_focuschange((ch.item_next?ch.item_next:this.item_children), IF_KFOCUSED); 138 | // result = TRUE; 139 | // } 140 | } 141 | } 142 | else 143 | result = FALSE; 144 | return result; 145 | }; 146 | /*void() mitem_tabs::item_resize = 147 | { 148 | local mitem ch; 149 | for (ch = this.item_children; ch; ch = ch.item_next) 150 | { 151 | ch.item_size = this.item_size; 152 | if (ch.item_resized) 153 | ch.item_resized(); 154 | } 155 | };*/ 156 | 157 | mitem_tabs(vector sz) menuitemtabs_spawn = 158 | { 159 | return spawn(mitem_tabs, item_size:sz); 160 | }; 161 | mitem_tab(mitem_tabs tabs, string itname) menuitemtab_spawn = 162 | { 163 | //a tab itself is little different from a frame, just has no implicit focus, and has a title 164 | mitem_tab n = spawn(mitem_tab, item_text:itname, frame_hasscroll:TRUE); 165 | 166 | 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'); 167 | return n; 168 | }; 169 | -------------------------------------------------------------------------------- /menusys/mitems_common.qc: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | simple block-fill item 3 | non-interactable. 4 | */ 5 | class mitem_fill : mitem 6 | { 7 | virtual void(vector pos) item_draw = 8 | { 9 | ui.drawfill(pos, this.item_size, this.item_rgb, this.item_alpha, 0); 10 | }; 11 | }; 12 | #define menuitemfill_spawn(sz,rgb,alph) spawn(mitem_fill, item_size:sz, item_rgb:rgb, item_alpha:alph) 13 | 14 | /*************************************************************************** 15 | basic picture item. 16 | non-interactable. 17 | 18 | item_text: the normal image to use 19 | item_text_mactive: the image to use when the mouse is over it. 20 | 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. 21 | */ 22 | class mitem_pic : mitem 23 | { 24 | string item_text_mactive; 25 | virtual void(vector pos) item_draw = 26 | { 27 | if (item_text_mactive != "" && (item_flags & IF_MFOCUSED)) 28 | ui.drawpic(pos, item_text_mactive, item_size, item_rgb, item_alpha, 0); 29 | else 30 | ui.drawpic(pos, item_text, item_size, item_rgb, item_alpha, 0); 31 | }; 32 | void() mitem_pic = 33 | { 34 | if (dp_workarounds) 35 | { 36 | if (substring(item_text, -4, 4) == ".lmp") 37 | item_text = substring(item_text, 0, -5); 38 | if (substring(item_text_mactive , -4, 5) == ".lmp") 39 | item_text_mactive = substring(item_text_mactive, 0, -4); 40 | } 41 | 42 | item_text = strzone(item_text); 43 | precache_pic(item_text); 44 | if (item_text_mactive) 45 | { 46 | item_text_mactive = strzone(item_text_mactive); 47 | precache_pic(item_text_mactive); 48 | } 49 | 50 | if (!item_size[0]) 51 | { 52 | float y = item_size[1]; 53 | item_size = drawgetimagesize(item_text); 54 | if (y) //rescale x to ma 55 | { 56 | if (!item_size[1]) //bad image? don't glitch out too much. 57 | item_size = item_size[0] * '1 1 0'; 58 | else 59 | item_size = [item_size[0] * (y / item_size[1]), y, 0]; 60 | } 61 | } 62 | }; 63 | virtual void() item_remove = 64 | { 65 | strunzone(item_text); 66 | if (item_text_mactive) 67 | strunzone(item_text_mactive); 68 | super::item_remove(); 69 | }; 70 | }; 71 | #define menuitempic_spawn(img,sz) spawn(mitem_pic, item_text:img, item_size:sz) 72 | 73 | /*************************************************************************** 74 | basic text item. 75 | interactable - executes a given console command. 76 | */ 77 | class mitem_text : mitem 78 | { 79 | virtual void(vector pos) item_draw = 80 | { 81 | vector rgb = menuitem_textcolour(this); 82 | float w; 83 | if (item_flags & IF_CENTERALIGN) 84 | { 85 | w = stringwidth(item_text, TRUE, '1 1 0'*item_scale); 86 | ui.drawstring(pos + [(item_size_x-w)/2, 0], item_text, '1 1 0' * item_scale, rgb, item_alpha, 0); 87 | } 88 | else if (item_flags & IF_RIGHTALIGN) 89 | { 90 | w = stringwidth(item_text, TRUE, '1 1 0'*item_scale); 91 | ui.drawstring(pos + [(item_size_x-w), 0], item_text, '1 1 0' * item_scale, rgb, item_alpha, 0); 92 | } 93 | else 94 | ui.drawstring(pos, item_text, '1 1 0' * item_scale, rgb, item_alpha, 0); 95 | }; 96 | virtual float(vector pos, float scan, float char, float down) item_keypress = 97 | { 98 | if (this.item_command) 99 | { 100 | if (!down) 101 | return FALSE; 102 | if (scan == K_ENTER || (scan == K_MOUSE1 && mouseinbox(pos, this.item_size))) 103 | { 104 | item_parent.item_execcommand(this, this.item_command); 105 | // localcmd(strcat(this.item_command, "\n")); 106 | return TRUE; 107 | } 108 | } 109 | return FALSE; 110 | }; 111 | 112 | //zone+unzone input strings as needed 113 | virtual void() item_remove = 114 | { 115 | strunzone(item_text); 116 | if (item_command) 117 | strunzone(item_command); 118 | super::item_remove(); 119 | }; 120 | void() mitem_text = 121 | { 122 | item_text = strzone(item_text); 123 | if (item_command != "") 124 | { 125 | item_command = strzone(item_command); 126 | item_flags |= IF_SELECTABLE; 127 | } 128 | }; 129 | }; 130 | mitem(string text, string command, float height) menuitemtext_spawn = 131 | { 132 | return spawn(mitem_text, item_scale:height, item_text:text, item_command:command, item_size:[stringwidth(text, TRUE, '1 1 0'*height), height, 0]); 133 | }; 134 | 135 | 136 | /*************************************************************************** 137 | basic text item. 138 | identical to text, but includes a 3dish border. 139 | */ 140 | class mitem_button : mitem 141 | { 142 | virtual void(vector pos) item_draw = 143 | { 144 | ui.drawfill(pos, [this.item_size[0], 1], TD_TOP, this.item_alpha, 0); 145 | ui.drawfill(pos, [1, this.item_size[1] - 1], TD_LFT, this.item_alpha, 0); 146 | ui.drawfill(pos + [this.item_size[0]-1, 1], [1, this.item_size[1] - 1], TD_RGT, this.item_alpha, 0); 147 | ui.drawfill(pos + [0, this.item_size[1]-1], [this.item_size[0], 1], TD_BOT, this.item_alpha, 0); 148 | 149 | pos_x += (this.item_size[0] - stringwidth(this.item_text, TRUE, '1 1 0'*this.item_scale)) * 0.5; 150 | pos_y += (this.item_size[1] - this.item_scale)*0.5; 151 | ui.drawstring(pos, this.item_text, '1 1 0' * this.item_scale, menuitem_textcolour(this), this.item_alpha, 0); 152 | }; 153 | virtual float(vector pos, float scan, float char, float down) item_keypress = 154 | { 155 | if (!down) 156 | return FALSE; 157 | if (scan == K_ENTER || (scan == K_MOUSE1 && mouseinbox(pos, this.item_size))) 158 | item_parent.item_execcommand(this, item_command); 159 | else 160 | return FALSE; 161 | return TRUE; 162 | }; 163 | virtual void() item_remove = 164 | { 165 | strunzone(item_text); 166 | if (item_command) 167 | strunzone(item_command); 168 | super::item_remove(); 169 | }; 170 | }; 171 | mitem_button(string text, string command, vector sz) menuitembutton_spawn = 172 | { 173 | mitem_button n = spawn(mitem_button); 174 | n.item_scale = sz_y - 4; 175 | n.item_text = strzone(text); 176 | n.item_size = sz; 177 | 178 | if (command != "") 179 | { 180 | n.item_command = strzone(command); 181 | n.item_flags |= IF_SELECTABLE; 182 | } 183 | return n; 184 | }; 185 | 186 | -------------------------------------------------------------------------------- /share/classes.qc: -------------------------------------------------------------------------------- 1 | #ifdef SSQC 2 | inline int* prng_state(int type) { return &self.prng_base[type]; } 3 | #else 4 | inline int* prng_state(int type) { return &pstate_pred.prng_base[type]; } 5 | #endif 6 | 7 | int lfsr_prng_raw(int v) { // A 16-bit xor-shift LFSR. 8 | v ^= (v >> 7); 9 | v ^= (v << 9); 10 | v &= 65535; 11 | v ^= (v >> 13); 12 | return v; 13 | } 14 | 15 | float lsfr_prng(int prev) { 16 | return lfsr_prng_raw(prev) / 65535.0; 17 | } 18 | 19 | float shared_prng(int type) { 20 | return (*prng_state(type) = lfsr_prng_raw(*prng_state(type))) / 65535.0; 21 | } 22 | 23 | float shared_crandom(int type) { 24 | return 2 * (shared_prng(type) - 0.5); 25 | } 26 | 27 | #ifdef CSQC 28 | float WP_GetAmmo(float ammo_type); 29 | float get_shells() { return WP_GetAmmo(AMMO_SHELLS); } 30 | static inline vector get_origin() { return PM_Org(); } 31 | #else 32 | float get_shells() { return self.ammo_shells; } 33 | static inline vector get_origin() { return self.origin; } 34 | #endif 35 | 36 | // Randomize by ammo index for consistency even if a packet/prediction misses. 37 | static float hwguy_random_index; 38 | 39 | void reset_hwguy_random() { 40 | hwguy_random_index = (*prng_state(PRNG_HWGUY) + get_shells() * 211) & 65535; 41 | if (hwguy_random_index == 0) 42 | hwguy_random_index = (get_shells() + 1) * 211; 43 | } 44 | 45 | float hwguy_random() { 46 | float index = hwguy_random_index; 47 | float r = (hwguy_random_index = lfsr_prng_raw(hwguy_random_index)) / 65535.0; 48 | return r; 49 | } 50 | 51 | float hwguy_crandom() { 52 | return 2 * (hwguy_random() - 0.5); 53 | } 54 | 55 | void FO_FireAssCanPellet(vector org, vector spread_dir, float var_speed, int index); 56 | 57 | void WeapPred_FireAssCan(vector vangle, float shotcount, 58 | vector spread) { 59 | vector bullet_dir, spread_dir, rand_dir, org; 60 | float bullet_speed, var_speed; 61 | 62 | reset_hwguy_random(); 63 | 64 | makevectors(vangle); 65 | 66 | // In front of player model and down towards gun 67 | bullet_speed = FPP_Get(FPP_ASSAULT_CANNON)->speed; 68 | bullet_dir = v_forward; 69 | 70 | for (int i = 0; i < shotcount; i++) { 71 | var_speed = hwguy_crandom()*10 + bullet_speed; // Slight speed variance 72 | rand_dir = (hwguy_crandom()*spread_x) * v_right + 73 | (hwguy_crandom()*spread_y) * v_up; 74 | spread_dir = bullet_dir + rand_dir; 75 | org = get_origin() + '0 0 10' + rand_dir; 76 | 77 | FO_FireAssCanPellet(org, spread_dir, var_speed, i); 78 | } 79 | } 80 | 81 | float Class_MaxSpeed(float playerclass) { 82 | switch (playerclass) { 83 | case PC_UNDEFINED: return SPEC_MAXSPEED; 84 | case PC_SCOUT: return PC_SCOUT_MAXSPEED; 85 | case PC_SNIPER: return PC_SNIPER_MAXSPEED; 86 | case PC_SOLDIER: return PC_SOLDIER_MAXSPEED; 87 | case PC_DEMOMAN: return PC_DEMOMAN_MAXSPEED; 88 | case PC_MEDIC: return PC_MEDIC_MAXSPEED; 89 | case PC_HVYWEAP: return PC_HVYWEAP_MAXSPEED; 90 | case PC_PYRO: return PC_PYRO_MAXSPEED; 91 | case PC_SPY: return PC_SPY_MAXSPEED; 92 | case PC_ENGINEER: return PC_ENGINEER_MAXSPEED; 93 | case PC_CIVILIAN: return PC_CIVILIAN_MAXSPEED; 94 | default: return 0; 95 | } 96 | } 97 | 98 | static float NextToWall(entity ent) { 99 | if ((pointcontents(ent.origin + [ent.maxs_x + 2, 0, 0]) == CONTENT_SOLID) || 100 | (pointcontents(ent.origin + [ent.mins_x - 2, 0, 0]) == CONTENT_SOLID) || 101 | (pointcontents(ent.origin + [0, ent.maxs_y + 2, 0]) == CONTENT_SOLID) || 102 | (pointcontents(ent.origin + [0, ent.mins_y - 2, 0]) == CONTENT_SOLID)) 103 | return TRUE; 104 | return FALSE; 105 | } 106 | 107 | const float CONC_P = 0.25; 108 | const float CONC_HZ = 1/CONC_P; 109 | 110 | void Conc_Stumble(entity ent, float stumble, float flip) { 111 | if ((ent.flags & (FL_INWATER | FL_ONGROUND) == 0) || 112 | (*self_tf_state() & TFSTATE_FEIGNED)) 113 | return; 114 | 115 | if (NextToWall(ent) && ent.velocity == '0 0 0') 116 | return; 117 | 118 | // [*]: This preserves what looks to be a long-standing bug where y-vel ends 119 | // up propagating due to missed temporary to hold vel_x. 120 | ent.velocity_x = flip * ent.velocity_y + stumble; 121 | ent.velocity_y = flip * ent.velocity_x + stumble; 122 | } 123 | 124 | void Conc_Jump(ConcState* cs, entity ent) { 125 | float stumble = shared_crandom(PRNG_CONC) * cs->mag / 10; 126 | float flip = (shared_prng(PRNG_CONC) < 0.5) ? -1 : 1; 127 | 128 | // See [*] above. 129 | ent.velocity_x = flip * ent.velocity_y + stumble; 130 | ent.velocity_y = flip * ent.velocity_x + stumble; 131 | } 132 | 133 | #ifdef SSQC 134 | static inline void ConcAction(entity ent, float itime, float mag, float flip) { 135 | Conc_Stumble(ent, mag, flip); 136 | } 137 | #else 138 | void PM_AddNudgeConc(float itime, float mag, float flip); 139 | static inline void ConcAction(entity ent, float itime, float mag, float flip) { 140 | PM_AddNudgeConc(itime, mag, flip); 141 | } 142 | #endif 143 | 144 | float Conc_Update(ConcState *cs, entity ent, float ctime) { 145 | if (!cs->mag || ctime < cs->next) 146 | return FALSE; 147 | 148 | while (ctime >= cs->next) { 149 | cs->mag = max(cs->mag - 1, 0); 150 | if (!cs->mag) { 151 | *self_tf_state() &= ~TFSTATE_CONC; 152 | break; 153 | } 154 | 155 | float mag = shared_crandom(PRNG_CONC) * cs->mag * 10; 156 | float flip = (shared_prng(PRNG_CONC) < 0.5) ? -1 : 1; 157 | ConcAction(ent, cs->next, mag, flip); 158 | cs->next += CONC_P; 159 | } 160 | 161 | return TRUE; 162 | } 163 | 164 | float Class_ScaleMoment(float playerclass, float damage) { 165 | if (playerclass != PC_HVYWEAP) 166 | return damage; 167 | return damage <= 50 ? 0 : damage / 4; 168 | } 169 | 170 | enum { 171 | kLaunch, 172 | kLand 173 | }; 174 | 175 | float NB_UseNewConc() { 176 | return fo_config.new_balance_flags & NBF_CONC_NEW_CAP; 177 | } 178 | 179 | float NB_NoCap() { 180 | return fo_config.new_balance_flags & NBF_NO_CAP; 181 | } 182 | 183 | 184 | void NB_ConcCap(entity ent, float speed) { 185 | vector planar_vel = [ent.velocity_x, ent.velocity_y, 0]; 186 | 187 | if (vlen(planar_vel) > speed) { 188 | planar_vel = normalize(planar_vel) * speed; 189 | } 190 | 191 | ent.velocity_x = planar_vel.x; 192 | ent.velocity_y = planar_vel.y; 193 | } 194 | 195 | void NB_ConcCapAction(entity ent, float player_class, float* tfstate, 196 | float gtime, float* cap_time, float action) { 197 | if (!NewBalanceActive() || !NB_UseNewConc()) 198 | return; 199 | 200 | const float kLockout = 0.5; 201 | 202 | if (action == kLaunch) { 203 | if (player_class == PC_MEDIC && !NB_NoCap()) { 204 | *tfstate |= TFSTATE_CONC_CAP; 205 | *cap_time = gtime + kLockout; 206 | } 207 | 208 | NB_ConcCap(ent, NB_CONC_CAP_AIR); 209 | } else if (action == kLand && (*tfstate & TFSTATE_CONC_CAP) && 210 | gtime > *cap_time) { 211 | float cap = *cap_time; 212 | *tfstate &= ~TFSTATE_CONC_CAP; 213 | NB_ConcCap(ent, NB_CONC_CAP_LAND); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /share/common_helpers.qc: -------------------------------------------------------------------------------- 1 | string ClassToString(float num) 2 | { 3 | if (num == 1) return "Scout"; 4 | if (num == 2) return "Sniper"; 5 | if (num == 3) return "Soldier"; 6 | if (num == 4) return "Demoman"; 7 | if (num == 5) return "Medic"; 8 | if (num == 6) return "HWGuy"; 9 | if (num == 7) return "Pyro"; 10 | if (num == 8) return "Spy"; 11 | if (num == 9) return "Engineer"; 12 | if (num == 11) return "Civilian"; 13 | if (num == 13) return "Sentry Gun"; 14 | if (num == 14) return "Goal Item"; 15 | return ""; 16 | } 17 | 18 | string TeamToString(float num) 19 | { 20 | if (num == 1) return "Blue"; 21 | if (num == 2) return "Red"; 22 | if (num == 3) return "Yellow"; 23 | if (num == 4) return "Green"; 24 | return " "; 25 | } 26 | 27 | void FO_Sound(entity e, float chan, string samp, float vol, float atten); 28 | 29 | #ifdef SSQC 30 | 31 | inline float ClientPred_Enabled(entity client, float pred_flag) { 32 | return infokeyf(client, "fo_wpp_status") & pred_flag; 33 | } 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /share/common_vote.qc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /share/debug.qc: -------------------------------------------------------------------------------- 1 | #ifdef SSQC 2 | 3 | #define IS_CSQC 0 4 | #define IS_SSQC 1 5 | 6 | // printf that works in any context 7 | #define printf(...) bprint(PRINT_HIGH, sprintf(__VA_ARGS__)) 8 | #define printd(...) dprint(sprintf(__VA_ARGS__)) 9 | 10 | #elif defined(CSQC) 11 | 12 | #define IS_CSQC 1 13 | #define IS_SSQC 0 14 | 15 | #define printf(...) print(sprintf(__VA_ARGS__)) 16 | 17 | #endif 18 | 19 | inline string qc_prefix() { 20 | #ifdef SSQC 21 | return "SSQC"; 22 | #else 23 | return "CSQC"; 24 | #endif 25 | } 26 | 27 | #define MSEC 0.001 28 | #define SEC 1 29 | 30 | #define errors(fmt, ...) error(sprintf("%s:%d Error> " fmt, __FILE__, __LINE__)) 31 | #define errorf(fmt, ...) error(sprintf("%s:%d Error> " fmt, __FILE__, __LINE__, __VA_ARGS__)) 32 | 33 | #define _ASSERT_OP(_op, _invop, _fmt, _val1, _v2) \ 34 | if (!((_val1) _op (_v2))) \ 35 | errorf("Expected: %s " #_op " %s, but: " #_fmt " " #_invop " " #_fmt "\n", #_val1, #_v2, _val1, _v2) 36 | 37 | #define __ASSERT_OP(_op, _invop, _fmt, _v1, _v2, __v1, __v2) \ 38 | if (!((__v1) _op (__v2))) \ 39 | errorf("Expected: %s " #_op " %s, but: " #_fmt " " #_invop " " #_fmt "\n", #_v1, #_v2, __v1, __v2) 40 | 41 | #define ASSERTF_EQ(_v1, _v2) _ASSERT_OP(==, !=, %0.2f, _v1, _v2) 42 | #define ASSERTF_NE(_v1, _v2) _ASSERT_OP(!=, ==, %0.2f, _v1, _v2) 43 | #define ASSERTF_GT(_v1, _v2) _ASSERT_OP( >, <=, %0.2f, _v1, _v2) 44 | #define ASSERTF_GE(_v1, _v2) _ASSERT_OP(>=, <, %0.2f, _v1, _v2) 45 | #define ASSERTF_LT(_v1, _v2) _ASSERT_OP( <, >=, %0.2f, _v1, _v2) 46 | #define ASSERTF_LE(_v1, _v2) _ASSERT_OP(<=, <, %0.2f, _v1, _v2) 47 | 48 | 49 | // Work around general weird behaviors with __int 50 | float __as_float(float v) { return v; } 51 | 52 | #define ASSERTD_EQ(_v1, _v2) __ASSERT_OP(==, !=, %d, _v1, _v2, __as_float(_v1), __as_float(_v2)) 53 | #define ASSERTD_NE(_v1, _v2) __ASSERT_OP(!=, ==, %d, _v1, _v2, __as_float(_v1), __as_float(_v2)) 54 | #define ASSERTD_GT(_v1, _v2) __ASSERT_OP( >, <=, %d, _v1, _v2, __as_float(_v1), __as_float(_v2)) 55 | #define ASSERTD_GE(_v1, _v2) __ASSERT_OP(>=, <, %d, _v1, _v2, __as_float(_v1), __as_float(_v2)) 56 | #define ASSERTD_LT(_v1, _v2) __ASSERT_OP( <, >=, %d, _v1, _v2, __as_float(_v1), __as_float(_v2)) 57 | #define ASSERTD_LE(_v1, _v2) __ASSERT_OP(<=, <, %d, _v1, _v2, __as_float(_v1), __as_float(_v2)) 58 | 59 | #define ASSERTS_EQ(_v1, _v2) _ASSERT_OP(==, !=, %s, _v1, _v2) 60 | #define ASSERTS_NE(_v1, _v2) _ASSERT_OP(!=, ==, %s, _v1, _v2) 61 | 62 | #define ASSERTF_TRUE(_v1) ASSERTF_EQ(_v1, TRUE) 63 | #define ASSERTF_FALSE(_v1) ASSERTF_EQ(_v1, FALSE) 64 | 65 | 66 | #define __CONCAT(_a, _b) _a ## _b 67 | #define _PRINT_ONCE(_n, ...) \ 68 | do { static float _n; if (!_n) { _n = TRUE; printf(__VA_ARGS__); } } while (0) 69 | #define _PRINT_EVERY(_n, _p, ...) \ 70 | do { static float _n; if (time > _n + _p) { _n = time + _p; printf(__VA_ARGS__); } } while (0) 71 | 72 | #define PRINT_ONCE(...) _PRINT_ONCE(__CONCAT(__once, __LINE__), __VA_ARGS__) 73 | #define PRINT_EVERY(_p, ...) _PRINT_EVERY(__CONCAT(__every, __LINE_), _p, __VA_ARGS__) 74 | 75 | #define EPS 0.001 76 | #define FEQ(_v1, _v2) (fabs(_v1 - _v2) < EPS) 77 | #define FNEQ(_v1, _v2) (fabs(_v1 - _v2) >= EPS) 78 | -------------------------------------------------------------------------------- /share/mcp_precache.qc: -------------------------------------------------------------------------------- 1 | void mcp_Precache() = 2 | { 3 | #define CN_HITTEXT "hittext" 4 | 5 | #define SND_HURTTEAM "hitaudio/hurtteam.wav" 6 | #define SND_HURTSELF "hitaudio/hurtself.wav" 7 | #define SND_HURTENEMY "hitaudio/hurtenemy.wav" 8 | #define SND_HURTENEMY_MEATSHOT "hitaudio/hurtenemy_meatshot.wav" 9 | precache_sound(SND_HURTTEAM); 10 | precache_sound(SND_HURTSELF); 11 | precache_sound(SND_HURTENEMY); 12 | precache_sound(SND_HURTENEMY_MEATSHOT); 13 | 14 | #define SND_NOARMOUR "hitaudio/noarmour.wav" 15 | precache_sound(SND_NOARMOUR); 16 | 17 | #define SND_KILLTEAM "hitaudio/killteam.wav" 18 | #define SND_KILLSELF "hitaudio/killself.wav" 19 | #define SND_KILLENEMY "hitaudio/killenemy.wav" 20 | precache_sound(SND_KILLTEAM); 21 | precache_sound(SND_KILLSELF); 22 | precache_sound(SND_KILLENEMY); 23 | 24 | 25 | #define SND_HEADSHOT "announcer/headshot.wav" 26 | precache_sound(SND_HEADSHOT); 27 | } -------------------------------------------------------------------------------- /ssqc/buttons.qc: -------------------------------------------------------------------------------- 1 | // button and multiple button 2 | 3 | #define BUTTON_START_OUT 32 4 | 5 | void () button_return; 6 | void (entity pe_player, float dontstopdead) Spy_CheckForFuncTouch; 7 | 8 | void () button_wait = { 9 | self.state = STATE_TOP; 10 | self.nextthink = self.ltime + self.wait; 11 | self.think = button_return; 12 | activator = self.enemy; 13 | SUB_UseTargets(); 14 | self.frame = 1; 15 | }; 16 | 17 | void () button_done = { 18 | self.state = STATE_BOTTOM; 19 | }; 20 | 21 | void () button_return = { 22 | // q3f support 23 | string targname; 24 | entity targ; 25 | targ = world; 26 | targname = self.target; 27 | targ = find(targ, targetname, targname); 28 | if(targ) targ.active = TFGS_INACTIVE; 29 | 30 | self.goal_state = TFGS_INACTIVE; 31 | self.state = STATE_DOWN; 32 | SUB_CalcMove(self.pos1, self.speed, button_done); 33 | self.frame = 0; 34 | if (self.health) 35 | self.takedamage = DAMAGE_YES; 36 | }; 37 | 38 | void () button_blocked = // do nothing, just don't come all the way back out 39 | { 40 | }; 41 | 42 | void () button_fire = { 43 | if ((self.state == STATE_UP) || (self.state == STATE_TOP)) 44 | return; 45 | 46 | // q3f support 47 | string targname; 48 | entity targ; 49 | targ = world; 50 | targname = self.target; 51 | targ = find(targ, targetname, targname); 52 | if(targ) targ.active = TFGS_ACTIVE; 53 | 54 | FO_Sound(self, CHAN_VOICE, self.noise, 1, ATTN_NORM); 55 | 56 | self.state = STATE_UP; 57 | SUB_CalcMove(self.pos2, self.speed, button_wait); 58 | }; 59 | 60 | void () button_use = { 61 | self.enemy = activator; 62 | button_fire(); 63 | }; 64 | 65 | void () button_touch = { 66 | local entity te; 67 | 68 | if (cb_prematch) 69 | return; 70 | 71 | if (other.classname != "player") 72 | return; 73 | 74 | Spy_CheckForFuncTouch(other, 0); 75 | 76 | if (self.goal_activation & TFGA_SPANNER) 77 | return; 78 | 79 | if (other.playerclass == 0) 80 | return; 81 | 82 | if (!Activated(self, other)) { 83 | if (self.else_goal != 0) { 84 | te = Findgoal(self.else_goal); 85 | if (te) 86 | AttemptToActivate(te, other, self); 87 | } 88 | return; 89 | } 90 | self.enemy = other; 91 | button_fire(); 92 | }; 93 | 94 | void () button_killed = { 95 | if (self.goal_activation & TFGA_SPANNER) 96 | return; 97 | 98 | self.enemy = damage_attacker; 99 | self.health = self.max_health; 100 | self.takedamage = DAMAGE_NO; 101 | button_fire(); 102 | }; 103 | 104 | void () func_button = { 105 | if (CheckExistence() == FALSE) { 106 | dremove(self); 107 | return; 108 | } 109 | 110 | if (self.sounds == 0) { 111 | precache_sound("buttons/airbut1.wav"); 112 | self.noise = "buttons/airbut1.wav"; 113 | } else if (self.sounds == 1) { 114 | precache_sound("buttons/switch21.wav"); 115 | self.noise = "buttons/switch21.wav"; 116 | } else if (self.sounds == 2) { 117 | precache_sound("buttons/switch02.wav"); 118 | self.noise = "buttons/switch02.wav"; 119 | } else if (self.sounds == 3) { 120 | //precache_sound("buttons/switch04.wav"); 121 | self.noise = "buttons/switch04.wav"; 122 | } 123 | 124 | SetMovedir(); 125 | 126 | // q3 support 127 | if (self.activetarget != "") { 128 | self.target = self.activetarget; 129 | } 130 | if (self.allowteams == "blue") { 131 | self.team_no = 1; 132 | } 133 | if (self.allowteams == "red") { 134 | self.team_no = 2; 135 | } 136 | 137 | 138 | self.movetype = MOVETYPE_PUSH; 139 | self.solid = SOLID_BSP; 140 | setmodel(self, self.model); 141 | 142 | self.blocked = button_blocked; 143 | self.use = button_use; 144 | 145 | if (self.health) { 146 | self.max_health = self.health; 147 | self.th_die = button_killed; 148 | self.takedamage = 1; 149 | } else 150 | self.touch = button_touch; 151 | 152 | if (!self.speed) 153 | self.speed = 40; 154 | if (!self.wait) 155 | self.wait = 1; 156 | if (!self.lip) 157 | self.lip = 4; 158 | 159 | self.state = STATE_BOTTOM; 160 | 161 | self.pos1 = self.origin; 162 | self.pos2 = 163 | self.pos1 + self.movedir * (fabs(self.movedir * self.size) - 164 | self.lip); 165 | 166 | if (self.spawnflags & BUTTON_START_OUT) 167 | button_fire(); 168 | }; 169 | -------------------------------------------------------------------------------- /ssqc/coop.qc: -------------------------------------------------------------------------------- 1 | //======================================================== 2 | // This file handles all the cooperative mode functions 3 | //======================================================== 4 | 5 | void () DroppedKeyThink = { 6 | self.think = SUB_Null; 7 | self.touch = key_touch; 8 | self.owner = world; 9 | }; 10 | 11 | void () DropKey = { 12 | if ((self.items & IT_KEY1) || (self.items & IT_KEY2)) { 13 | newmis = spawn(); 14 | if (self.items & IT_KEY1) { 15 | self.items = self.items - (self.items & IT_KEY1); 16 | newmis.items = IT_KEY1; 17 | if (world.worldtype == 0) { 18 | FO_SetModel(newmis, "progs/w_s_key.mdl"); 19 | newmis.netname = "silver key"; 20 | newmis.noise = "misc/medkey.wav"; 21 | } else if (world.worldtype == 1) { 22 | FO_SetModel(newmis, "progs/m_s_key.mdl"); 23 | newmis.netname = "silver runekey"; 24 | newmis.noise = "misc/runekey.wav"; 25 | } else if (world.worldtype == 2) { 26 | FO_SetModel(newmis, "progs/b_s_key.mdl"); 27 | newmis.netname = "silver keycard"; 28 | newmis.noise = "misc/basekey.wav"; 29 | } 30 | } else if (self.items & IT_KEY2) { 31 | self.items = self.items - (self.items & IT_KEY2); 32 | newmis.items = IT_KEY2; 33 | if (world.worldtype == 0) { 34 | FO_SetModel(newmis, "progs/w_g_key.mdl"); 35 | newmis.netname = "gold key"; 36 | newmis.noise = "misc/medkey.wav"; 37 | } else if (world.worldtype == 1) { 38 | FO_SetModel(newmis, "progs/m_g_key.mdl"); 39 | newmis.netname = "gold runekey"; 40 | newmis.noise = "misc/runekey.wav"; 41 | } else if (world.worldtype == 2) { 42 | FO_SetModel(newmis, "progs/b_g_key.mdl"); 43 | newmis.netname = "gold keycard"; 44 | newmis.noise = "misc/basekey.wav"; 45 | } 46 | } 47 | newmis.owner = self; 48 | newmis.touch = SUB_Null; 49 | setorigin(newmis, self.origin + '0 0 16'); 50 | makevectors(self.v_angle); 51 | newmis.velocity = normalize(v_forward) * 300 + '0 0 200'; 52 | newmis.movetype = MOVETYPE_TOSS; 53 | newmis.solid = SOLID_TRIGGER; 54 | newmis.deadflag = DEAD_DYING; 55 | setsize(newmis, VEC_HULL_MIN, VEC_HULL_MAX); 56 | newmis.think = DroppedKeyThink; 57 | newmis.nextthink = time + 1.5; 58 | } else 59 | sprint(self, PRINT_HIGH, "You do not have a key\n"); 60 | }; 61 | 62 | float () DoorShouldOpen = { 63 | local entity ptr; 64 | local float plyrcount; 65 | local entity plyr1; 66 | local entity plyr2; 67 | 68 | if (coop != 2) 69 | return (1); 70 | 71 | plyr1 = world; 72 | plyr2 = world; 73 | 74 | plyrcount = 0; 75 | ptr = find(world, classname, "player"); 76 | while (ptr != world) { 77 | if (!(ptr.tf_items & self.items) && (ptr.playerclass != 0.000) 78 | && (ptr.solid != 0.000) && (ptr.model != string_null)) { 79 | 80 | plyrcount = plyrcount + 1; 81 | if (plyrcount == 1) 82 | plyr1 = ptr; 83 | else if (plyrcount == 2) 84 | plyr2 = ptr; 85 | } 86 | ptr = find(ptr, classname, "player"); 87 | } 88 | if (plyrcount != 0) { 89 | if (plyrcount == 1) { 90 | bprint(PRINT_HIGH, plyr1.netname, " needs"); 91 | } else if (plyrcount == 2) { 92 | bprint(PRINT_HIGH, plyr1.netname, " and ", plyr2.netname, 93 | " need"); 94 | } else { 95 | bprint(PRINT_HIGH, "More players need"); 96 | } 97 | bprint(PRINT_HIGH, " to unlock the "); 98 | if (self.items & IT_KEY1) 99 | bprint(PRINT_HIGH, "silver"); 100 | else 101 | bprint(PRINT_HIGH, "gold"); 102 | 103 | bprint(PRINT_HIGH, " door\n"); 104 | return (0); 105 | } 106 | bprint(PRINT_HIGH, "The "); 107 | if (self.items & IT_KEY1) 108 | bprint(PRINT_HIGH, "silver"); 109 | else 110 | bprint(PRINT_HIGH, "gold"); 111 | 112 | bprint(PRINT_HIGH, " door has been unlocked\n"); 113 | return (1); 114 | }; 115 | -------------------------------------------------------------------------------- /ssqc/ctf.qc: -------------------------------------------------------------------------------- 1 | //========================================== 2 | // Functions for CTF Support 3 | //========================================== 4 | 5 | void () TeamFortress_CTF_FlagInfo = { 6 | local entity te; 7 | 8 | te = Finditem(CTF_FLAG1); 9 | if (te.goal_state == TFGS_ACTIVE) { 10 | if (self == te.owner) { 11 | sprint(self, PRINT_HIGH, "You have the enemy flag. "); 12 | } else { 13 | sprint(self, PRINT_HIGH, te.owner.netname, " has"); 14 | if (self.team_no == 1) 15 | sprint(self, PRINT_HIGH, " your flag. "); 16 | else 17 | sprint(self, PRINT_HIGH, " the enemy flag. "); 18 | } 19 | } else if (te.origin != te.oldorigin) { 20 | if (self.team_no == 1) 21 | sprint(self, PRINT_HIGH, "Your flag is lying about. "); 22 | else 23 | sprint(self, PRINT_HIGH, "The enemy flag is lying about. "); 24 | } else if (self.team_no == 1) 25 | sprint(self, PRINT_HIGH, "Your flag is in your base. "); 26 | else 27 | sprint(self, PRINT_HIGH, "The enemy flag is in their base. "); 28 | 29 | te = Finditem(CTF_FLAG2); 30 | if (te.goal_state == TFGS_ACTIVE) { 31 | if (self == te.owner) { 32 | sprint(self, PRINT_HIGH, "You have the enemy flag.\n"); 33 | } else { 34 | sprint(self, PRINT_HIGH, te.owner.netname, " has"); 35 | if (self.team_no == 2) 36 | sprint(self, PRINT_HIGH, " your flag.\n"); 37 | else 38 | sprint(self, PRINT_HIGH, " the enemy flag.\n"); 39 | } 40 | } else if (te.origin != te.oldorigin) { 41 | if (self.team_no == 2) 42 | sprint(self, PRINT_HIGH, "Your flag is lying about.\n"); 43 | else 44 | sprint(self, PRINT_HIGH, "The enemy flag is lying about.\n"); 45 | } else if (self.team_no == 2) 46 | sprint(self, PRINT_HIGH, "Your flag is in your base.\n"); 47 | else 48 | sprint(self, PRINT_HIGH, "The enemy flag is in their base.\n"); 49 | }; 50 | -------------------------------------------------------------------------------- /ssqc/debug.qc: -------------------------------------------------------------------------------- 1 | float () CheckExistence; 2 | entity(float gno) Findgoal; 3 | 4 | void (entity te) dremove = 5 | { 6 | if (te == world) { 7 | dprint("***BUG BUG BUG BUG BUG BUG BUG BUG BUG BUG***\n"); 8 | dprint("WORLD has nearly been removed. Don't worry\n"); 9 | dprint("***BUG BUG BUG BUG BUG BUG BUG BUG BUG BUG***\n"); 10 | return; 11 | } 12 | if (te.is_removed == TRUE) 13 | return; 14 | 15 | te.is_removed = TRUE; 16 | remove(te); 17 | }; 18 | 19 | .float created_at; // Forward dec 20 | void dremove_sent(entity te) { 21 | static const float epsilon = 2*SERVER_FRAME_DT; 22 | 23 | float expires = te.created_at + epsilon; // In the past for 0 created at 24 | 25 | if (time > expires) { 26 | dremove(te); 27 | } else { 28 | te.nextthink = expires; 29 | te.think = SUB_Remove; 30 | } 31 | } 32 | 33 | void () display_location = { 34 | local string st; 35 | 36 | st = vtos(self.origin); 37 | sprint(self, PRINT_HIGH, "Location : ", st, "\n"); 38 | st = vtos(self.angles); 39 | sprint(self, PRINT_HIGH, "Angles : ", st, "\n"); 40 | 41 | }; 42 | -------------------------------------------------------------------------------- /ssqc/extraents.qc: -------------------------------------------------------------------------------- 1 | void (float tno, float scoretoadd) TeamFortress_TeamIncreaseScore; 2 | void (float all) TeamFortress_TeamShowScores; 3 | void () InitTrigger; 4 | void FO_SetModel(entity e, string fomdl); 5 | 6 | /* ====== Vote Ball ======= */ 7 | 8 | float() crandom; 9 | 10 | void (vector o, float z) ball_kick = { 11 | 12 | local vector v; 13 | 14 | v_x = ((o_x * 1.65) + (random() * 20) ); 15 | v_y = ((o_y * 1.65) + (random() * 20) ); 16 | 17 | if (o_x < 0.000) { 18 | o_x = o_x * -1.000; 19 | } 20 | if (o_y < 0.000) { 21 | o_y = o_y * -1.000; 22 | } 23 | 24 | v_z = ((100 + (random() * 30)) + ((o_y + o_x) * 0.20) + (z * 2.5) ); 25 | 26 | 27 | self.flags = self.flags - ( self.flags & 512.000); 28 | self.velocity = v; 29 | 30 | }; 31 | 32 | void (vector o) ball_fly = { 33 | local vector v; 34 | 35 | v_x = (o_x * 0.600) + (crandom() * 40); 36 | v_y = (o_y * 0.600) + (crandom() * 40); 37 | 38 | if (o_x < 0.000) { 39 | o_x = o_x * -1.000; 40 | } 41 | if (o_y < 0.000) { 42 | o_y = o_y * -1.000; 43 | } 44 | 45 | 46 | v_z = (140 + (crandom() * 40)) + ((o_y + o_x) * 0.20); 47 | 48 | self.flags = self.flags - ( self.flags & 512.000); 49 | self.velocity = v; 50 | 51 | 52 | }; 53 | 54 | void () ball_touch = { 55 | if (self.watertype == CONTENT_LAVA) { 56 | /*self.velocity_x = 0; 57 | self.velocity_y = 100; 58 | self.velocity_z = 0;*/ 59 | setorigin (self,self.oldorigin); 60 | return; 61 | } 62 | 63 | if (other.classname == "worldspawn") { 64 | return; 65 | } 66 | 67 | if (round_over) { 68 | return; 69 | } 70 | if (other.classname == "player") { 71 | self.oldenemy = other; 72 | ball_kick(other.velocity, (other.v_angle_x * -1.000)); 73 | return; 74 | 75 | } 76 | self.oldenemy = other.owner; 77 | ball_fly(other.velocity); 78 | return; 79 | }; 80 | 81 | void () ball_reset = { 82 | self.solid = 1.000; 83 | self.velocity = '0 0 0'; 84 | self.origin = self.oldorigin; 85 | setorigin (self,self.origin); 86 | 87 | if ( !droptofloor () ) { 88 | dprint ("GoalItem (ball) fell out of level at "); 89 | dprint (vtos (self.origin)); 90 | dprint ("\n"); 91 | dremove (self); 92 | return ; 93 | } 94 | }; 95 | 96 | void () item_ball = { 97 | self.solid = SOLID_TRIGGER; 98 | self.movetype = MOVETYPE_BOUNCE; 99 | //self.flags= FL_ITEM; 100 | 101 | if ( self.mdl ) { 102 | precache_model (self.mdl); 103 | precache_model2 (self.mdl); 104 | FO_SetModel (self,self.mdl); 105 | } else { 106 | self.mdl = "progs/lavaball.mdl"; 107 | FO_SetModel (self,self.mdl); 108 | } 109 | 110 | if ( !self.netname ) { 111 | self.netname = "ball"; 112 | } 113 | 114 | if ( (self.goal_min == '0 0 0') ) { 115 | self.goal_min = '-12 -12 -12'; 116 | } 117 | if ( (self.goal_max == '0 0 0') ) { 118 | self.goal_max = '12 12 12'; 119 | } 120 | setsize (self,self.goal_min,self.goal_max); 121 | 122 | //setsize (self,'-0 -0 -0','24 24 24'); 123 | setorigin (self,self.origin); 124 | self.oldorigin = self.origin; 125 | self.touch = ball_touch; 126 | }; 127 | 128 | void () soccer_goal_touch = { 129 | if (round_over) return; 130 | 131 | if (cb_prematch) return; 132 | 133 | if (other.classname == "item_ball") { 134 | if (other.solid) { 135 | TeamFortress_TeamIncreaseScore (self.owned_by,self.count); 136 | TeamFortress_TeamShowScores (2); 137 | sound (self,FL_ITEM,self.noise,1,ATTN_NONE); 138 | //round_winner = self.pteam; 139 | other.solid = SOLID_NOT; 140 | other.nextthink = (time + 0.3); 141 | other.think = ball_reset; 142 | } 143 | return; 144 | } 145 | }; 146 | 147 | void () info_soccer_goal = { 148 | if (!self.noise ) { 149 | self.noise = "items/tf2kfgc.wav"; 150 | } 151 | 152 | precache_sound (self.noise); 153 | 154 | if (self.owned_by == 1) { 155 | //self.pteam = pteam1; 156 | } 157 | else if (self.owned_by == 2) { 158 | //self.pteam = pteam2; 159 | } 160 | else if (self.owned_by == 3) { 161 | //self.pteam = pteam3; 162 | } 163 | else if (self.owned_by == 4) { 164 | //self.pteam = pteam4; 165 | } 166 | InitTrigger(); 167 | 168 | self.touch = soccer_goal_touch; 169 | 170 | }; 171 | -------------------------------------------------------------------------------- /ssqc/flare.qc: -------------------------------------------------------------------------------- 1 | //==================================== 2 | // This file handles all the functions 3 | // to deal with the flare 'grenade'. 4 | //==================================== 5 | 6 | void () FlareGrenadeTouch = { 7 | FO_Sound(self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); 8 | if (pointcontents(self.origin) == CONTENT_SKY) { 9 | dremove(self); 10 | return; 11 | } 12 | if (other == world) { 13 | self.movetype = MOVETYPE_NONE; 14 | self.velocity = '0 0 0'; 15 | } 16 | if (self.velocity == '0 0 0') { 17 | self.avelocity = '0 0 0'; 18 | self.touch = SUB_Null; 19 | } 20 | }; 21 | 22 | void () FlareGrenadeThink = { 23 | local float rnum; 24 | local float time_left; 25 | 26 | time_left = self.health - time; 27 | if (time_left > 33) { 28 | 29 | rnum = random(); 30 | if (rnum < 0.5) 31 | self.effects = EF_DIMLIGHT; 32 | else 33 | self.effects = 0; 34 | 35 | self.nextthink = time + 0.05 + random() * 0.1; 36 | 37 | } else if (time_left > 31) { 38 | 39 | rnum = random(); 40 | if (rnum < 0.5) 41 | self.effects = EF_BRIGHTLIGHT; 42 | else 43 | self.effects = EF_DIMLIGHT; 44 | 45 | self.nextthink = time + 0.05 + random() * 0.1; 46 | 47 | } else if (time_left > 15) { 48 | 49 | self.effects = EF_BRIGHTLIGHT; 50 | self.nextthink = time + 10; 51 | 52 | } else if (time_left < 1) { 53 | 54 | RemoveFlare(); 55 | 56 | } else { 57 | self.effects = 8; 58 | self.nextthink = time + time_left; 59 | } 60 | }; 61 | 62 | void () FlareGrenadeExplode = { 63 | self.skin = 1; 64 | self.health = time + 40; 65 | self.nextthink = time + 0.05 + random() * 0.1; 66 | self.think = FlareGrenadeThink; 67 | }; 68 | 69 | void () RemoveFlare = { 70 | self.effects = self.effects - (self.effects & EF_BRIGHTLIGHT); 71 | dremove(self); 72 | }; 73 | -------------------------------------------------------------------------------- /ssqc/fo_math.qc: -------------------------------------------------------------------------------- 1 | class fo_math : entity 2 | { 3 | float value; 4 | float min_value; 5 | float max_value; 6 | 7 | float add; 8 | float subtract; 9 | float multiply; 10 | float divide; 11 | 12 | float activate_goal_no_on_max_value; 13 | float activate_goal_no_on_min_value; 14 | 15 | float perform_reset_on_max_value; 16 | float perform_reset_on_min_value; 17 | float reset_value_on_max_value; 18 | float reset_value_on_min_value; 19 | 20 | // state 21 | float hit_max_value; 22 | float hit_min_value; 23 | 24 | nonvirtual void (float hit_value, float activate_goal_no) ValidateValue = 25 | { 26 | entity act = world; 27 | 28 | // only execute once after it is past the max/min value 29 | if (!hit_value) 30 | { 31 | if (activate_goal_no) 32 | { 33 | act = findfloat(world, ::goal_no, activate_goal_no); 34 | if (act) 35 | AttemptToActivate(act, other, this); 36 | } 37 | } 38 | }; 39 | 40 | nonvirtual void () Process = 41 | { 42 | entity act = world; 43 | value = value + add; 44 | if (value > max_value) 45 | { 46 | ValidateValue(hit_max_value, activate_goal_no_on_max_value); 47 | hit_max_value = TRUE; 48 | 49 | if (perform_reset_on_max_value) 50 | { 51 | value = reset_value_on_max_value; 52 | } 53 | } 54 | 55 | act = world; 56 | value = value - subtract; 57 | if (value < min_value) 58 | { 59 | ValidateValue(hit_min_value, activate_goal_no_on_min_value); 60 | hit_min_value = TRUE; 61 | 62 | if (perform_reset_on_min_value) 63 | value = reset_value_on_min_value; 64 | } 65 | }; 66 | }; -------------------------------------------------------------------------------- /ssqc/fo_misc_info.qc: -------------------------------------------------------------------------------- 1 | class fo_misc_info : entity 2 | { 3 | void () fo_misc_info = 4 | { 5 | if (this.mdl) 6 | { 7 | precache_model(this.mdl); 8 | precache_model2(this.mdl); 9 | this.model = this.mdl; 10 | 11 | if (this.spawnflags & FO_MISC_INFO_NO_Z_TEST) 12 | { 13 | this.nextthink = time + .01; 14 | } 15 | else 16 | { 17 | FO_SetModel(this, this.mdl); 18 | } 19 | 20 | if (this.goal_state == TFGS_REMOVED) 21 | RemoveGoal(this); 22 | } 23 | }; 24 | 25 | virtual void () think = 26 | { 27 | this.nextthink = time + .01; 28 | }; 29 | }; -------------------------------------------------------------------------------- /ssqc/functions.qc: -------------------------------------------------------------------------------- 1 | float (float tno) TeamFortress_TeamSet; 2 | string (float tno) TeamFortress_TeamGetColor; 3 | string (entity pov, float tno) TeamFortress_TeamGetColorFor; 4 | void (entity p) SetTeamName; 5 | void (entity pl) Menu_Close; 6 | 7 | void (float tno) playerSetTeam = { 8 | local string st; 9 | TeamFortress_TeamSet(tno); 10 | self.team_no = tno; 11 | stuffcmd(self, "color "); 12 | st = TeamFortress_TeamGetColor(tno); 13 | stuffcmd(self, st); 14 | stuffcmd(self, "\n"); 15 | SetTeamName(self); 16 | }; 17 | 18 | float () PlayerCount = { 19 | local entity te; 20 | local float tmp = 0; 21 | 22 | te = find(world, classname, "player"); 23 | while (te != world) { 24 | tmp = tmp + 1; 25 | te = find(te, classname, "player"); 26 | } 27 | return tmp; 28 | }; 29 | 30 | float () SpectatorCount = { 31 | local entity te; 32 | local float tmp = 0; 33 | 34 | te = find(world, classname, "observer"); 35 | while (te != world) { 36 | tmp = tmp + 1; 37 | te = find(te, classname, "observer"); 38 | } 39 | return tmp; 40 | }; 41 | 42 | void () nextCaptain = { 43 | local entity te; 44 | 45 | te = find(world, classname, "player"); 46 | while (te != world) { 47 | if (te.captain == 4) 48 | te.captain = 3; 49 | else if (te.captain == 3) 50 | te.captain = 2; 51 | else if (te.captain == 2) { 52 | te.captain = 1; 53 | stuffcmd(te,"reload\n"); // Required to send one impulse in order to show menu 54 | } else if (te.captain == 1) 55 | te.captain = number_of_teams; 56 | te = find(te, classname, "player"); 57 | } 58 | }; 59 | 60 | void () disableCaptain = { 61 | captainmode = 0; 62 | bprint(2, "\x10\sCaptain Mode\s\x11\s:\s Captain Mode Disabled!\n"); 63 | }; 64 | 65 | void () randomizeCaptains = { 66 | local entity te; 67 | local float tmp = 0; 68 | local float teamno = 0; 69 | local float capteam[4] = {0, 0, 0, 0}; 70 | 71 | te = find(world, classname, "player"); 72 | while (te != world) { 73 | if (te.captain == 9 && tmp < number_of_teams) { 74 | do { 75 | teamno = floor((random() * number_of_teams)); 76 | } while (capteam[teamno] == 1); 77 | capteam[teamno] = 1; 78 | te.captain = teamno + 1; 79 | if (te.captain == 1) 80 | stuffcmd(te, "reload\n"); 81 | tmp = tmp + 1; 82 | } 83 | te = find(te, classname, "player"); 84 | } 85 | }; 86 | 87 | void () randomizeTeams = { 88 | local entity te, temp; 89 | local float tmp = 0; 90 | local float teamno = 0; 91 | local float randteam[4] = {0, 0, 0, 0}; 92 | 93 | te = find(world, classname, "player"); 94 | while (te != world) { 95 | if (tmp >= number_of_teams) { 96 | tmp = 0; 97 | randteam[0] = 0; 98 | randteam[1] = 0; 99 | randteam[2] = 0; 100 | randteam[3] = 0; 101 | } 102 | if (tmp < number_of_teams) { 103 | do { 104 | teamno = floor((random() * number_of_teams)); 105 | } while (randteam[teamno] == 1); 106 | 107 | randteam[teamno] = 1; 108 | temp = self; 109 | self = te; 110 | playerSetTeam(teamno + 1); 111 | self = temp; 112 | tmp = tmp + 1; 113 | } 114 | te = find(te, classname, "player"); 115 | } 116 | }; 117 | 118 | string (string text) clearString = { 119 | local float i; 120 | string specialChars[60] = {"�", "", "","","","","","",""," "," "," ","","","","","","","",""," ","?","","‚","ƒ","„","…","†","‡","ˆ","‰","Š","‹","Œ","","Ž","","","‘","œ","Å","Å“","","ž","Ÿ", "<", ">", "|", ":", "*", "?", "\\", "/", "\"", "&", "~", "`", ",", " ", "."}; 121 | text = strconv(1,1,1,text); 122 | text = strireplace("__", "_", text); 123 | for (i = 0; i < 58; i++) { 124 | text = strireplace(specialChars[i], "", text); 125 | } 126 | 127 | return text; 128 | } 129 | 130 | void () PrintLoginMessage = { 131 | 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"); 132 | return; 133 | } 134 | 135 | void () EndGameThink = { 136 | local string m = mapname; 137 | if(vote_result != string_null && vote_result != "") { 138 | m = vote_result; 139 | } else if(nextmap != string_null && nextmap != "") { 140 | m = nextmap; 141 | } 142 | localcmd("changelevel "); 143 | localcmd(m); 144 | localcmd("\n"); 145 | dremove(self); 146 | } 147 | 148 | void () MapEndSequence = { 149 | local entity player; 150 | local entity maprestarttimer; 151 | player = find (world, classname, "player"); 152 | while (player) 153 | { 154 | if (player.playerclass != 0 && self.has_disconnected != 1) { 155 | local float timeplayed = gametime - player.classtime; 156 | LogEventChangeClass(player, player.playerclass, 0, timeplayed); 157 | player.classtime = gametime; 158 | } 159 | player = find (player, classname, "player"); 160 | } 161 | LogEventTeamScores(); 162 | LogEventGameEnd(); 163 | if (logfilehandle > 0) 164 | fclose(logfilehandle); 165 | canlog = 0; 166 | 167 | maprestarttimer = spawn(); 168 | maprestarttimer.classname = "timer"; 169 | maprestarttimer.netname = "maprestarttimer"; 170 | maprestarttimer.think = EndGameThink; 171 | maprestarttimer.nextthink = time + map_restart_time; 172 | } 173 | -------------------------------------------------------------------------------- /ssqc/helpers.qc: -------------------------------------------------------------------------------- 1 | enum { 2 | kRegionUS = 1, 3 | kRegionEU, 4 | kRegionOCE, 5 | kRegionLeague, 6 | kRegionUnknown, 7 | }; 8 | 9 | struct RegionMatch { 10 | string channel; 11 | float region; 12 | }; 13 | 14 | static RegionMatch region_matches[] = { 15 | { "504171613793681408", kRegionUS }, // US pug 16 | { "513699536846323712", kRegionEU }, // EU pug 17 | { "542237808895459338", kRegionOCE }, // OCE pug 18 | { "1147341454851719219", kRegionLeague }, // Scrim 19 | { "1026405619231625257", kRegionLeague }, // Tourney 20 | }; 21 | 22 | float ServerRegion() { 23 | static float region; 24 | 25 | if (region) 26 | return region; 27 | 28 | // Can also query FO_REGION env but that does not appear to be 29 | // reliably/consistently set at the moment. 30 | region = kRegionUnknown; 31 | for (int i = 0; i < region_matches.length; i++) { 32 | if (discord_channel_id == region_matches[i].channel) { 33 | region = region_matches[i].region; 34 | break; 35 | } 36 | } 37 | 38 | return region; 39 | } 40 | 41 | float ServerIsOCE() { 42 | if (ServerRegion() == kRegionOCE) 43 | return TRUE; 44 | 45 | // Additional check beyond kRegion for picking up OCE Scrim/Tourney etc 46 | // This is pretty awful, for at least these servers we should just fix FO_REGION 47 | // to be consistent in the future. 48 | string hostname = serverkey("hostname"); 49 | 50 | return strstrofs(hostname, "Sydney") >= 0 || 51 | strstrofs(hostname, "Melbourne") >= 0 || 52 | strstrofs(hostname, "New Zealand") >= 0; 53 | } 54 | 55 | float ServerIsStaging() { 56 | string hostname = serverkey("hostname"); 57 | 58 | return strstrofs(hostname, "Staging") >= 0; 59 | } 60 | -------------------------------------------------------------------------------- /ssqc/hwguy.qc: -------------------------------------------------------------------------------- 1 | void AssCanBulletThink () 2 | { 3 | self.movetype = MOVETYPE_TOSS; 4 | self.think = SUB_Remove; 5 | 6 | if (asscanrangedie > 0) { 7 | self.nextthink = time + asscanrangedie; 8 | } else { 9 | self.nextthink = time + 3; 10 | } 11 | } 12 | 13 | void AssCanBulletTouch() 14 | { 15 | if (self.voided) 16 | return; // Marked for removal 17 | self.voided = TRUE; 18 | 19 | if (other == self.owner) 20 | return; // Touching self, do nothing 21 | 22 | deathmsg = self.weapon; 23 | if (other.health) 24 | { 25 | TF_T_Damage(other, self, self.owner, 8, TF_TD_NOTTEAM, 26 | TF_TD_OTHER); 27 | WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); 28 | WriteByte(MSG_MULTICAST, TE_BLOOD); 29 | WriteByte(MSG_MULTICAST, 1); 30 | WriteCoord(MSG_MULTICAST, self.origin_x); 31 | WriteCoord(MSG_MULTICAST, self.origin_y); 32 | WriteCoord(MSG_MULTICAST, self.origin_z); 33 | multicast(self.origin, MULTICAST_PVS); 34 | } 35 | else 36 | { 37 | WriteByte(MSG_MULTICAST, SVC_TEMPENTITY); 38 | WriteByte(MSG_MULTICAST, TE_GUNSHOT); 39 | WriteByte(MSG_MULTICAST, 1); 40 | WriteCoord(MSG_MULTICAST, self.origin_x); 41 | WriteCoord(MSG_MULTICAST, self.origin_y); 42 | WriteCoord(MSG_MULTICAST, self.origin_z); 43 | multicast(self.origin, MULTICAST_PVS); 44 | } 45 | 46 | dremove_sent(self); 47 | }; 48 | 49 | void FO_FireAssCanPellet(vector org, vector dir, float proj_speed, int index) 50 | { 51 | local float num; 52 | 53 | entity proj = FOProj_Create(FPP_ASSAULT_CANNON); 54 | proj.owner = self; 55 | proj.classname = "proj_bullet"; 56 | 57 | proj.movetype = MOVETYPE_FLY; // Small collision 58 | 59 | proj.solid = SOLID_BBOX; 60 | proj.touch = AssCanBulletTouch; 61 | 62 | if (asscanrange > 0) { 63 | num = (1 / proj_speed) * asscanrange; 64 | proj.think = AssCanBulletThink; 65 | proj.nextthink = time + num; // Projectile range / gravity 66 | } else { 67 | proj.think = SUB_Remove; 68 | proj.nextthink = time + 5; // Stop projectile going forever 69 | } 70 | 71 | proj.frame = hwguy_random()*15; // Full range of sizes 72 | proj.skin = 16 + hwguy_random()*7; // Bright colours 73 | proj.weapon = DMSG_ASSAULTCANNON; 74 | 75 | proj.velocity = dir * proj_speed; // Constant speed multiplier 76 | proj.angles = vectoangles(dir); // Create direction angle 77 | setorigin (proj, org); // Move to starting position 78 | 79 | proj.fpp.ammo_index = self.ammo_shells * 100 + index; 80 | FOProj_Finalize(proj); 81 | } 82 | 83 | void StopAssCan() { 84 | if (self.tfstate & TFSTATE_AC_FIRING) 85 | player_asscan_down1(); 86 | } 87 | 88 | void FO_LockToggle () { 89 | self.tfstate ^= TFSTATE_LOCK; 90 | 91 | Status_Refresh(self); 92 | /* self.impulse = 0; */ 93 | /* this shouldn't be here, I think we just need to allow this impulse while shooting */ 94 | /* worth checking detpipes for this too */ 95 | } 96 | 97 | float AssCanTryBeginFire() { 98 | if (get_shells() < 1) 99 | return FALSE; 100 | 101 | if (FO_CheckForReload()) 102 | return FALSE; 103 | 104 | if (!W_ConsumeAmmoIfPossible(AMMO_CELLS, PC_HVYWEAP_CELL_FIRE)) { 105 | // Not worth optimizing w/ client side. 106 | if (time >= self.antispam_assault_cannon) { 107 | sprint(self, PRINT_MEDIUM, "Not enough cells to power up the Assault Cannon\n"); 108 | self.antispam_assault_cannon = time + 3; 109 | } 110 | return FALSE; 111 | } 112 | 113 | player_asscan_up1(); 114 | return TRUE; 115 | } 116 | -------------------------------------------------------------------------------- /ssqc/locfiles.qc: -------------------------------------------------------------------------------- 1 | #pragma target fte 2 | string prepq3fstring (string s); 3 | float locationMultiplier; 4 | 5 | void loadloc() { 6 | locationMultiplier = TRUE; 7 | local float i; 8 | local string ln; 9 | local string out; 10 | local float locfilehandle; 11 | 12 | local string path = strcat("locs/", mapname); 13 | path = strcat(path, ".loc"); 14 | locfilehandle = fopen(path, FILE_READ); 15 | if (locfilehandle >= 0) { 16 | ln = fgets(locfilehandle); 17 | while (ln) { // test for null 18 | if (ln != "") // test for empty 19 | { 20 | numlocs++; 21 | } 22 | 23 | ln = fgets(locfilehandle); 24 | } 25 | fclose(locfilehandle); 26 | 27 | locs = memalloc(sizeof(*locs)*numlocs); 28 | 29 | locfilehandle = fopen(path, FILE_READ); 30 | ln = fgets(locfilehandle); 31 | i = 0; 32 | while (ln && i < numlocs) { 33 | ln = strtrim(ln); 34 | if (ln != "") 35 | { 36 | float s1 = strstrofs(ln, " ", 0); 37 | float s2 = strstrofs(ln, " ", s1+1); 38 | float s3 = strstrofs(ln, " ", s2+1); 39 | 40 | local string px = substring(ln, 0, s1); 41 | local string py = substring(ln, s1+1, (s2-(s1+1))); 42 | local string pz = substring(ln, s2+1, (s3-(s2+1))); 43 | 44 | locs[i].pos.x = stof(px); 45 | locs[i].pos.y = stof(py); 46 | locs[i].pos.z = stof(pz); 47 | locs[i].desc = substring(ln, s3+1, strlen(ln)-s3+2); 48 | i++; 49 | } 50 | 51 | ln = fgets(locfilehandle); 52 | } 53 | fclose(locfilehandle); 54 | out = strcat("Loaded ", mapname); 55 | out = strcat(out, ".loc with "); 56 | out = strcat(out, ftos(numlocs)); 57 | out = strcat(out, " locations\n"); 58 | } 59 | else { 60 | out = strcat("Couldn't find ", mapname); 61 | out = strcat(out, ".loc\n"); 62 | 63 | // check for q3f target_locations 64 | numlocs = 0; 65 | entity targ; 66 | targ = find(world, classname, "target_location"); 67 | while (targ) 68 | { 69 | numlocs++; 70 | targ = find(targ, classname, "target_location"); 71 | } 72 | 73 | if (numlocs > 0) 74 | { 75 | locationMultiplier = FALSE; 76 | locs = memalloc(sizeof(*locs)*numlocs); 77 | targ = find(world, classname, "target_location"); 78 | i = 0; 79 | while (targ) 80 | { 81 | locs[i].pos = targ.origin; 82 | locs[i].desc = strtrim(prepq3fstring(targ.message)); 83 | i++; 84 | targ.nextthink = time + 0.01; 85 | targ.think = SUB_Remove; 86 | targ = find(targ, classname, "target_location"); 87 | } 88 | 89 | out = "Loaded locs from target_location entities\n"; 90 | } 91 | } 92 | dprint(out); 93 | } 94 | 95 | string getLocationName(vector location) { 96 | local float bestdist; 97 | local string desc; 98 | local float i; 99 | 100 | desc = "someplace"; 101 | location = location * ((locationMultiplier == TRUE) ? 8 : 1); 102 | bestdist = 0; 103 | for (i = 0; i < numlocs; i++) { 104 | float dist = vlen(location - locs[i].pos); 105 | if (bestdist == 0) { 106 | bestdist = dist; 107 | desc = locs[i].desc; 108 | } 109 | else if (dist < bestdist) { 110 | bestdist = dist; 111 | desc = locs[i].desc; 112 | } 113 | } 114 | return desc; 115 | } -------------------------------------------------------------------------------- /ssqc/login.qc: -------------------------------------------------------------------------------- 1 | void (entity player, string login, string secret) performLogin = { 2 | string data = strcat("login=", login); 3 | data = strcat(data,"&secret="); 4 | data = strcat(data,secret); 5 | uri_get(loginUrl, BR_LOGIN_REQUEST, "application/x-www-form-urlencoded", data); 6 | self.login_in_progress = 1; 7 | dprint(infokey(self,"name")); 8 | dprint(" ["); 9 | dprint(infokey(self,"ip")); 10 | dprint("]"); 11 | dprint(" trying to login as "); 12 | dprint(login); 13 | dprint("\n"); 14 | } 15 | 16 | void (entity player, string token) performFoLogin = { 17 | local string uri = sprintf("%s/results/api/v1/fo_login", backend_address); 18 | uri_get(uri, FO_LOGIN_REQUEST, "application/json", sprintf("{ \"auth_token\": \"%s\" }", token)); 19 | dprint(sprintf("%s [%s] logging in\n", infokey(player, "name"), infokey(player, "ip"))); 20 | } 21 | 22 | void(float reqid, float responsecode, string resourcebody, int resourcebytes) URI_Get_Callback = { 23 | local float success = !responsecode; 24 | 25 | switch(reqid) { 26 | case FO_QUAD_STARTED_REQUEST: 27 | local string msg; 28 | 29 | if (success && resourcebody) { 30 | match_id = resourcebody; 31 | msg = sprintf("Quad start successfully logged. ID: %s\n", match_id); 32 | bprint(PRINT_HIGH, msg); 33 | dprint(msg); 34 | } else { 35 | msg = sprintf("Quad start failed to log. Response code: %d\n", responsecode); 36 | bprint(PRINT_HIGH, msg); 37 | dprint(msg); 38 | } 39 | 40 | break; 41 | case FO_QUAD_FINISHED_REQUEST: 42 | local string msg; 43 | 44 | if (success && resourcebody == match_id) { 45 | msg = sprintf("Quad result successfully logged. ID: %s\n", match_id); 46 | bprint(PRINT_HIGH, msg); 47 | dprint(msg); 48 | } else { 49 | msg = sprintf("Quad result failed to log. Response code: %d\n", responsecode); 50 | bprint(PRINT_HIGH, msg); 51 | dprint(msg); 52 | } 53 | 54 | break; 55 | case FO_LOGIN_REQUEST: 56 | local string msg; 57 | 58 | if (!responsecode) { 59 | self.fo_login = resourcebody; 60 | CenterPrint(self, sprintf("You have logged in as %s\n", self.fo_login)); 61 | msg = sprintf("%s logged in as %s\n", infokey(self, "name"), self.fo_login); 62 | bprint(PRINT_HIGH, msg); 63 | dprint(msg); 64 | } else { 65 | CenterPrint(self, "Login failed\n"); 66 | msg = (sprintf("%s login failed. Response code: %d\n", infokey(self, "name"), responsecode)); 67 | sprint(self, PRINT_HIGH, msg); 68 | dprint(msg); 69 | } 70 | 71 | break; 72 | case BR_LOGIN_REQUEST: 73 | local float got_one = 0; 74 | self.login_in_progress = 0; 75 | local float csqcactive = infokeyf(self, INFOKEY_P_CSQCACTIVE); 76 | if (!responsecode) { 77 | float num_args = tokenizebyseparator(resourcebody,";"); 78 | self.login = argv(0); 79 | forceinfokey(self,"*login", self.login); 80 | bprint(2, infokey(self,"name")); 81 | bprint(2, " has logged in as \s"); 82 | bprint(2, self.login); 83 | bprint(2, "\s\n"); 84 | dprint(infokey(self,"name")); 85 | dprint(" logged in as "); 86 | dprint(self.login); 87 | dprint("\n"); 88 | CenterPrint3(self, "You have logged in as ", self.login, "\n"); 89 | if (num_args > 1) { 90 | if (argv(1) == "true") { 91 | self.is_admin = TRUE; 92 | forceinfokey(self,"*admin", ftos(self.is_admin)); 93 | Admin_Aliases (); 94 | bprint(2, self.netname, " \sgains full admin status!\s\n"); 95 | // bprint (3, "\n"); 96 | sprint(self, 2, "Type \scommands\s for admin commands.\n"); 97 | } 98 | } 99 | if(self.team_no == 0 && !intermission_running) { 100 | if (clanbattle && (self.has_disconnected != 1)) { 101 | if (self.tf_id != 0) { 102 | got_one = RejoinWithTfId(); 103 | ResetAndRespawnPlayer(self); 104 | } 105 | if (!got_one) { 106 | CreateTfIdAndJoin(); 107 | } 108 | } 109 | if (!got_one) { 110 | if (csqcactive) 111 | Menu_Team(0); 112 | else 113 | Menu_Team(1); 114 | } 115 | } 116 | } else { 117 | dprint(infokey(self,"name")); 118 | dprint(" login failed: "); 119 | dprint(self.login); 120 | dprint("\n"); 121 | CenterPrint3(self, ftos(responsecode), " - Login FAILED, invalid Login/Secret", "\n"); 122 | } 123 | break; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /ssqc/medic.qc: -------------------------------------------------------------------------------- 1 | // COMBAT MEDIC FUNCTIONS 2 | // ====================== 3 | 4 | // functions by order of appearance 5 | void () CF_Medic_AuraToggle; 6 | 7 | // initializes the medic healing aura 8 | // called from weapons.qc:ImpulseCommands() 9 | void () CF_Medic_AuraToggle = { 10 | if (!medicaura || self.playerclass != PC_MEDIC) 11 | return; 12 | 13 | if (self.aura_active) { 14 | self.aura_active = 0; 15 | sprint(self, PRINT_HIGH, "Healing aura turned off\n"); 16 | } 17 | else { 18 | self.aura_active = 1; 19 | sprint(self, PRINT_HIGH, "Healing aura turned on\n"); 20 | } 21 | Status_Refresh(self); 22 | }; 23 | 24 | float (entity pe_target) CF_Medic_AuraHealPlayer = { 25 | local float f_healamount = 0; 26 | 27 | if (pe_target.health < pe_target.max_health) { 28 | f_healamount = min(PC_MEDIC_AURA_HEAL_AMOUNT, (pe_target.max_health - pe_target.health)); 29 | pe_target.health = pe_target.health + f_healamount; 30 | } 31 | 32 | return f_healamount; 33 | }; 34 | 35 | // heals the team players around the medic every second 36 | // called from tfort.qc:TeamFortress_SetEquipment() 37 | void () CF_Medic_AuraFindPlayers = { 38 | local float f_healamount = 0; 39 | local float f_healtotal = 0; 40 | local float f_healcount = 0; 41 | local entity e_find; 42 | 43 | self.nextthink = time + PC_MEDIC_AURA_HEAL_TIME; 44 | 45 | if (!self.owner.aura_active || self.owner.ammo_cells < ceil(PC_MEDIC_MAXAMMO_CELL / 2)) 46 | return; 47 | 48 | e_find = findradius(self.owner.origin, PC_MEDIC_AURA_RANGE); 49 | while (e_find != world) { 50 | if (e_find != self.owner && e_find.classname == "player" && e_find.team_no == self.owner.team_no) { 51 | f_healamount = CF_Medic_AuraHealPlayer(e_find); 52 | f_healtotal = f_healtotal + f_healamount; 53 | if (f_healamount) 54 | f_healcount = f_healcount + 1; 55 | } 56 | e_find = e_find.chain; 57 | } 58 | 59 | if (f_healtotal) { 60 | FO_Sound(self.owner, CHAN_ITEM, "items/r_item2.wav", 1, 1); 61 | if (time < self.owner.aura_healtime) 62 | self.owner.aura_healamount = self.owner.aura_healamount + f_healtotal; 63 | else 64 | self.owner.aura_healamount = f_healtotal; 65 | self.owner.aura_healtime = time + 3; 66 | self.owner.aura_healcount = f_healcount; 67 | self.owner.ammo_cells = self.owner.ammo_cells - f_healtotal; 68 | Status_Refresh(self.owner); 69 | } else if (time >= self.owner.aura_healtime && self.owner.aura_healcount) { 70 | self.owner.aura_healcount = 0; 71 | self.owner.aura_healamount = 0; 72 | self.owner.aura_healtime = 0; 73 | } 74 | 75 | self.owner.ammo_cells = self.owner.ammo_cells - ceil(PC_MEDIC_MAXAMMO_CELL / 20); 76 | }; 77 | 78 | // increases the medic's cells two times per second 79 | // called from tfort.qc:TeamFortress_SetEquipment() 80 | void () CF_Medic_RegenerateCells = { 81 | local entity oldself; 82 | 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)); 83 | 84 | // skip this regen tick if on cooldown 85 | if (time <= self.owner.regen_cooldown) { 86 | self.nextthink = time + 1; 87 | return; 88 | } 89 | 90 | self.nextthink = time + PC_MEDIC_CELL_REGEN_TIME; 91 | 92 | if (self.owner.ammo_cells < PC_MEDIC_MAXAMMO_CELL) { 93 | self.owner.ammo_cells = f_newcells; 94 | 95 | // refresh status bar if cells just hit max 96 | if (f_newcells >= PC_MEDIC_MAXAMMO_CELL) 97 | Status_Refresh(self.owner); 98 | else if (f_newcells >= ceil(PC_MEDIC_MAXAMMO_CELL / 2) && f_newcells < ceil(PC_MEDIC_MAXAMMO_CELL / 2 + PC_MEDIC_MAXAMMO_CELL / 10)) 99 | Status_Refresh(self.owner); 100 | } 101 | 102 | // Update drop ammo menu if cells are 103 | if ((self.owner.menu_input == Menu_Drop_Input) && (self.owner.ammo_cells >= DROP_CELLS)) { 104 | oldself = self; 105 | self = self.owner; 106 | Menu_Drop(); 107 | self = oldself; 108 | } 109 | }; 110 | 111 | // regenerates the medic's health 112 | // called from tfort.qc:TeamFortress_SetEquipment() 113 | void () CF_Medic_Regenerate = { 114 | self.nextthink = time + PC_MEDIC_REGEN_TIME; 115 | 116 | if (self.owner.has_disconnected == 1) { 117 | dremove(self); 118 | return; 119 | } 120 | 121 | // only regen if half of max or more cells with new medikit 122 | if (self.owner.health >= self.owner.max_health || self.owner.ammo_cells < ceil(PC_MEDIC_MAXAMMO_CELL / 2)) { 123 | return; 124 | } 125 | 126 | if (self.owner.ammo_medikit < PC_MEDIC_REGEN_AMOUNT) { 127 | self.owner.health = self.owner.health + self.owner.ammo_medikit; 128 | self.owner.ammo_medikit = 0; 129 | } else { 130 | self.owner.health = self.owner.health + PC_MEDIC_REGEN_AMOUNT; 131 | self.owner.ammo_medikit = self.owner.ammo_medikit - PC_MEDIC_REGEN_AMOUNT; 132 | } 133 | if (self.owner.health > self.owner.max_health) 134 | self.owner.health = self.owner.max_health; 135 | } 136 | 137 | void () BioInfection_Decay = { 138 | local entity te; 139 | local entity Bio; 140 | 141 | if ((teamplay & TFSTATE_INFECTED) && self.owner.team_no == self.enemy.team_no && self.owner.team_no) { 142 | self.owner.tfstate = self.owner.tfstate - (self.owner.tfstate & TFSTATE_INFECTED); 143 | dremove(self); 144 | return; 145 | } else if (self.invincible_finished > time) { 146 | self.owner.tfstate = self.owner.tfstate - (self.owner.tfstate & TFSTATE_INFECTED); 147 | dremove(self); 148 | return; 149 | } 150 | 151 | // stop infection timer if no longer infected or if medic 152 | if (!(self.owner.tfstate & TFSTATE_INFECTED) || self.owner.playerclass == PC_MEDIC) { 153 | dremove(self); 154 | return; 155 | } 156 | 157 | // poison teammates that come too close 158 | te = findradius(self.owner.origin, 80); 159 | while (te != world && te != self.owner) { 160 | if (te.classname == "player" && te.deadflag == 0 && te.playerclass) { 161 | if (!(te.tfstate & TFSTATE_INFECTED)) { 162 | if (te.playerclass != PC_MEDIC) { 163 | if (!((teamplay & TEAMPLAY_NOEXPLOSIVE) && self.owner.team_no == self.enemy.team_no && self.owner.team_no)) { 164 | Bio = spawn(); 165 | Bio.nextthink = 2; 166 | Bio.think = BioInfection_Decay; 167 | Bio.owner = te; 168 | Bio.classname = "timer"; 169 | Bio.enemy = self.enemy; 170 | 171 | te.tfstate = te.tfstate | TFSTATE_INFECTED; 172 | te.infection_team_no = self.owner.infection_team_no; 173 | LogEventAffliction(self.enemy, te, TFSTATE_INFECTED); 174 | LogEventAffliction(self.owner, te, TFSTATE_INFECTED); 175 | sprint(te, PRINT_MEDIUM, "You have been infected by ", self.owner.netname, "\n"); 176 | sprint(self.owner, PRINT_MEDIUM, "You have infected ", te.netname, "\n"); 177 | } 178 | } 179 | } 180 | } 181 | te = te.chain; 182 | } 183 | if (old_biodamage) 184 | self.nextthink = time + 3; 185 | else 186 | self.nextthink = time + 1; 187 | deathmsg = DMSG_BIOWEAPON; 188 | TF_T_Damage(self.owner, self, self.enemy, 8, TF_TD_IGNOREARMOR, TF_TD_OTHER); 189 | SpawnBlood(self.owner.origin, 30); 190 | }; 191 | 192 | void () BioInfection_MonsterDecay = { 193 | self.nextthink = time + 2; 194 | 195 | T_Damage(self.enemy, self, self.owner, 5); 196 | SpawnBlood(self.enemy.origin, 20); 197 | 198 | if (self.enemy.health < 1) 199 | dremove(self); 200 | }; 201 | -------------------------------------------------------------------------------- /ssqc/monsters.qc: -------------------------------------------------------------------------------- 1 | float (entity targ) visible = { 2 | local vector spot1, spot2; 3 | 4 | spot1 = self.origin + self.view_ofs; 5 | spot2 = targ.origin + targ.view_ofs; 6 | traceline(spot1, spot2, 1, self); 7 | if (trace_inopen && trace_inwater) 8 | return (FALSE); 9 | 10 | if (trace_fraction == 1) 11 | return (TRUE); 12 | 13 | return (FALSE); 14 | }; 15 | 16 | void () monster_ogre = { 17 | dremove(self); 18 | }; 19 | 20 | void () monster_knight = { 21 | dremove(self); 22 | }; 23 | 24 | void () monster_shambler = { 25 | dremove(self); 26 | }; 27 | 28 | void () monster_demon1 = { 29 | dremove(self); 30 | }; 31 | 32 | void () monster_wizard = { 33 | dremove(self); 34 | }; 35 | 36 | void () monster_zombie = { 37 | dremove(self); 38 | }; 39 | 40 | void () monster_dog = { 41 | dremove(self); 42 | }; 43 | 44 | void () monster_hell_knight = { 45 | dremove(self); 46 | }; 47 | 48 | void () monster_tarbaby = { 49 | dremove(self); 50 | }; 51 | 52 | void () monster_vomit = { 53 | dremove(self); 54 | }; 55 | 56 | void () monster_enforcer = { 57 | dremove(self); 58 | }; 59 | 60 | void () monster_shalrath = { 61 | dremove(self); 62 | }; 63 | 64 | void () monster_dragon = { 65 | dremove(self); 66 | }; 67 | 68 | void () monster_army = { 69 | dremove(self); 70 | }; 71 | 72 | void () t_movetarget; 73 | 74 | void () movetarget_f = { 75 | if (!self.targetname) 76 | objerror("monster_movetarget: no targetname"); 77 | 78 | self.solid = SOLID_TRIGGER; 79 | self.touch = t_movetarget; 80 | setsize(self, '-8 -8 -8', '8 8 8'); 81 | }; 82 | 83 | void () path_corner = { 84 | if (CheckExistence() == FALSE) { 85 | dremove(self); 86 | return; 87 | } 88 | movetarget_f(); 89 | }; 90 | 91 | void () t_movetarget = { 92 | local entity temp; 93 | 94 | if (other.movetarget != self) 95 | return; 96 | 97 | if (other.enemy) 98 | return; 99 | 100 | temp = self; 101 | self = other; 102 | other = temp; 103 | self.movetarget = find(world, targetname, other.target); 104 | self.goalentity = find(world, targetname, other.target); 105 | self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin); 106 | if (!self.movetarget) { 107 | 108 | self.pausetime = (time + 999999); 109 | self.th_stand(); 110 | return; 111 | } 112 | 113 | }; 114 | /* 115 | float (float v) anglemod = { 116 | while (v >= 360) 117 | v = v - 360; 118 | while (v < 0) 119 | v = v + 360; 120 | return (v); 121 | }; 122 | */ 123 | float (entity targ) range = { 124 | local vector spot1; 125 | local vector spot2; 126 | local float r; 127 | 128 | spot1 = self.origin + self.view_ofs; 129 | spot2 = targ.origin + targ.view_ofs; 130 | 131 | r = vlen(spot1 - spot2); 132 | if (r < 120) 133 | return (RANGE_MELEE); 134 | 135 | if (r < 500) 136 | return (RANGE_NEAR); 137 | 138 | if (r < 1000) 139 | return (RANGE_MID); 140 | 141 | return (RANGE_FAR); 142 | }; 143 | 144 | float (entity targ) infront = { 145 | local vector vec; 146 | local float dot; 147 | 148 | makevectors(self.angles); 149 | vec = normalize(targ.origin - self.origin); 150 | dot = vec * v_forward; 151 | if (dot > 0.3) 152 | return (1); 153 | return (0); 154 | }; 155 | 156 | void () HuntTarget = { 157 | self.goalentity = self.enemy; 158 | self.think = self.th_run; 159 | self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin); 160 | self.nextthink = time + 0.1; 161 | SUB_AttackFinished(1); 162 | }; 163 | 164 | float () FindTarget = { 165 | local entity client; 166 | 167 | client = checkclient(); 168 | if (!client) 169 | return (FALSE); 170 | 171 | if (client.flags & FL_NOTARGET) 172 | return (FALSE); 173 | 174 | if (client.items & IT_INVISIBILITY) 175 | return (FALSE); 176 | 177 | if (!visible(client)) 178 | return (FALSE); 179 | 180 | if (client.classname != "player") 181 | return (FALSE); 182 | 183 | self.enemy = client; 184 | HuntTarget(); 185 | return (TRUE); 186 | }; 187 | -------------------------------------------------------------------------------- /ssqc/mvdsv.qc: -------------------------------------------------------------------------------- 1 | float () UserCmd = 2 | { 3 | float isProcessed; 4 | string arg1, arg2, arg3; 5 | arg1 = argv_mvdsv(0); 6 | arg2 = argv_mvdsv(1); 7 | arg3 = argv_mvdsv(2); 8 | 9 | isProcessed = ParseCmds(arg1, arg2, arg3); 10 | return (isProcessed); 11 | }; 12 | 13 | float (string cmd) ConsoleCmd { 14 | local float arg_num; 15 | local string arg_val0, arg_val1, arg_val2; 16 | local string tmp; 17 | 18 | if(cmd) { 19 | //FTESV implementation 20 | arg_num = tokenize(cmd); 21 | arg_val0 = argv(0); 22 | arg_val1 = argv(1); 23 | arg_val2 = argv(2); 24 | switch(argv(0)) { 25 | case "vote_addmap": 26 | AddVoteMap(argv(1),argv(2),argv(3),stof(argv(4)),stof(argv(5)),stof(argv(6))); 27 | return TRUE; 28 | case "vote_removemap": 29 | RemoveVoteMap(argv(1)); 30 | return TRUE; 31 | case "nextmap": 32 | if(FO_GetUserSetting (world, "nomapcycle", "nmc", "0")) { 33 | bprint(PRINT_HIGH, "Tried setting next map to ", argv(1), ", but nomapcycle is set.\n"); 34 | } else { 35 | nextmap = argv(1); 36 | bprint(PRINT_HIGH, "Next map set to ", nextmap, ". Enable nomapcycle to skip.\n"); 37 | } 38 | return TRUE; 39 | } 40 | } else { 41 | //MVDSV implementation 42 | arg_val0 = argv_mvdsv(0); 43 | arg_val1 = argv_mvdsv(1); 44 | arg_val2 = argv_mvdsv(2); 45 | arg_num = argc(); 46 | } 47 | if (arg_num == 0) { 48 | return (0); 49 | } 50 | if (arg_val0 == "prematch") { 51 | if (arg_num == 2) { 52 | localcmd("localinfo prematch "); 53 | localcmd(arg_val1); 54 | localcmd("\n"); 55 | return (1); 56 | } 57 | if (arg_num == 1) { 58 | tmp = infokey (world, arg_val0); 59 | dprint("prematch is "); 60 | dprint("\""); 61 | dprint(tmp); 62 | dprint("\"\n"); 63 | return (1); 64 | } 65 | } 66 | else if (arg_val0 == "autorecord") 67 | { 68 | if (arg_num == 2) { 69 | localcmd("localinfo demo_auto_left "); 70 | localcmd(arg_val1); 71 | localcmd("\n"); 72 | return (1); 73 | } 74 | if (arg_num == 1) { 75 | tmp = infokey (world, "demo_auto_left"); 76 | if ((stof(tmp) > 0)) { 77 | dprint("Auto-Recording off\n"); 78 | localcmd("localinfo demo_auto_left 0\n"); 79 | return (1); 80 | } 81 | else { 82 | dprint("Auto-Recording the next match\n"); 83 | localcmd("localinfo demo_auto_left 1\n"); 84 | return (1); 85 | } 86 | } 87 | } 88 | else if (arg_val0 == "clan") 89 | { 90 | ClanMode(); 91 | return (1); 92 | } 93 | else if (arg_val0 == "quadmode") 94 | { 95 | QuadMode(); 96 | return (1); 97 | } 98 | else if (arg_val0 == "duelmode") 99 | { 100 | DuelMode(); 101 | return (1); 102 | } 103 | return 0; 104 | } 105 | 106 | void (float duration) GE_PausedTic = { 107 | local entity p; 108 | if (unpause_requested && (unpause_countdown == 0)) { 109 | unpause_countdown = duration + 5; 110 | unpause_lastcountnumber = 5; 111 | } 112 | if (unpause_requested) { 113 | if ((duration >= unpause_countdown)) { 114 | is_paused = 0; 115 | cs_paused = 0; 116 | unpause_countdown = 0; 117 | unpause_requested = 0; 118 | unpause_lastcountnumber = 0; 119 | setpause(0); 120 | NotifyPauseUnpause(FALSE); 121 | } else if (duration >= unpause_countdown - (unpause_lastcountnumber)) { 122 | unpause_lastcountnumber--; 123 | p = find (world, classname, "player"); 124 | while (p) { 125 | if (p.netname != "") 126 | stuffcmd(p, "play buttons/switch04.wav\n"); 127 | p = find(p, classname, "player"); 128 | } 129 | bprint(PRINT_HIGH, 130 | sprintf("Unpausing in %d seconds\n", unpause_lastcountnumber)); 131 | } 132 | } 133 | } 134 | 135 | void(float pauseduration) SV_PausedTic = { 136 | GE_PausedTic(pauseduration); 137 | } 138 | -------------------------------------------------------------------------------- /ssqc/progs.src: -------------------------------------------------------------------------------- 1 | #pragma target fte_5768 2 | #pragma optimise 3 3 | #pragma optimise no-cf 4 | #pragma flag enable subscope 5 | #pragma flag enable iffloat 6 | #pragma flag enable lo 7 | 8 | #pragma progs_dat "../qwprogs.dat" 9 | 10 | #define QWSSQC 11 | 12 | #includelist 13 | ../share/fteextensions.qc 14 | ../share/debug.qc 15 | defs.qc 16 | ../share/commondefs.qc 17 | ../share/common_helpers.qc 18 | qw.qc 19 | debug.qc 20 | time.qc 21 | ../share/physics.qc 22 | ../share/weapons.qc 23 | ../share/prediction.qc 24 | ../share/classes.qc 25 | ../share/animate.qc 26 | ../share/mcp_precache.qc 27 | helpers.qc 28 | events.qc 29 | roles.qc 30 | q3defs.qc 31 | status.qc 32 | teamplay.qc 33 | functions.qc 34 | menu.qc 35 | csmenu.qc 36 | ../share/common_vote.qc 37 | vote.qc 38 | extraents.qc 39 | help.qc 40 | subs.qc 41 | items.qc 42 | locfiles.qc 43 | rewind.qc 44 | combat.qc 45 | weapons.qc 46 | world.qc 47 | client.qc 48 | player.qc 49 | doors.qc 50 | buttons.qc 51 | triggers.qc 52 | tforttm.qc 53 | plats.qc 54 | misc.qc 55 | monsters.qc 56 | flare.qc 57 | sentry.qc 58 | boss.qc 59 | admin.qc 60 | scout.qc 61 | sniper.qc 62 | tsoldier.qc 63 | demoman.qc 64 | medic.qc 65 | hwguy.qc 66 | pyro.qc 67 | spy.qc 68 | engineer.qc 69 | camera.qc 70 | clan.qc 71 | quadmode.qc 72 | tfort.qc 73 | tforthlp.qc 74 | tfortmap.qc 75 | login.qc 76 | commands.qc 77 | ctf.qc 78 | coop.qc 79 | actions.qc 80 | spect.qc 81 | q3.qc 82 | mvdsv.qc 83 | rotate.qc 84 | fo_logic.qc 85 | fo_math.qc 86 | fo_misc_info.qc 87 | #endlist 88 | -------------------------------------------------------------------------------- /ssqc/q3defs.qc: -------------------------------------------------------------------------------- 1 | // q3 defs - enable q3f map ents 2 | .string allowteams; 3 | .string give; 4 | .string active_all_sound; 5 | .string model; 6 | .float teamscore; 7 | .string active_all_message; 8 | .string holding; 9 | .string groupname; 10 | .string carried_all_message; 11 | .string carried_message; 12 | .string carried_all_sound; 13 | .string inactive_all_message; 14 | .string activetarget; 15 | .string checkstate; 16 | .float active; 17 | .string initialstate; 18 | .string flagsq3; // .float flags already exists and q3 uses it as a string 19 | .string failtarget; 20 | .string disabled_all_message; 21 | 22 | string __fullspawndata; // due to flags/flagsq3 -------------------------------------------------------------------------------- /ssqc/roles.qc: -------------------------------------------------------------------------------- 1 | #pragma target fte 2 | 3 | float (string ps_short, string ps_setting, string ps_default) CF_GetSetting; 4 | string(float tno) GetTeamName; 5 | void () item_tfgoal_touch; 6 | void CenterPrint(entity, string); 7 | float IsFeigned(entity ent); 8 | 9 | 10 | float quad_roles; 11 | 12 | typedef struct { 13 | string name; 14 | string prefix; 15 | float detpipe_limit; 16 | float respawn_delay_time; 17 | float gren1_limits[10]; 18 | float gren2_limits[10]; 19 | float class_limits[12]; 20 | } Team_Role; 21 | 22 | var Team_Role Role_None = {"Default", "", 6}; 23 | var Team_Role Role_Attack = {"Attack", "att_", 6}; 24 | var Team_Role Role_Defence = {"Defence", "def_", 6}; 25 | Team_Role * Team1_Role; 26 | Team_Role * Team2_Role; 27 | Team_Role * Team3_Role; 28 | Team_Role * Team4_Role; 29 | 30 | const string CLASSES [12] = { 31 | "observer", 32 | "scout", 33 | "sniper", 34 | "soldier", 35 | "demoman", 36 | "medic", 37 | "hwguy", 38 | "pyro", 39 | "spy", 40 | "engineer", 41 | "randompc", 42 | "civilian" 43 | }; 44 | 45 | void (Team_Role * role) LoadRole = { 46 | role.detpipe_limit = CF_GetSetting(strcat(role.prefix,"dl"), strcat(role.prefix,"detpipe_limit"), ftos(Role_None.detpipe_limit)); 47 | role.respawn_delay_time = CF_GetSetting(strcat(role.prefix,"rd"), strcat(role.prefix,"respawn_delay"), ftos(Role_None.respawn_delay_time)); 48 | role.gren1_limits[0] = 0; 49 | role.gren2_limits[0] = 0; 50 | role.class_limits[0] = 0; 51 | for(float i = 1; i < 10; i++) { 52 | 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])); 53 | 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])); 54 | role.class_limits[i] = CF_GetSetting(strcat(role.prefix,"cr_",CLASSES[i]), strcat(role.prefix,"cr_",CLASSES[i]), ftos(Role_None.class_limits[i])); 55 | } 56 | }; 57 | 58 | void (entity e) PrintRoleStatus = { 59 | float current = CF_GetSetting("qr", "quad_roles", "0"); 60 | sprint(e, PRINT_HIGH, "Roles are ", quad_roles?"\sENABLED\s":"\sDISABLED\s"); 61 | if(!quadmode) { 62 | sprint(e, PRINT_HIGH, ", however it requires \squadmode\s to work. Set it from the admin menu."); 63 | } 64 | if(current != quad_roles) { 65 | sprint(e, PRINT_HIGH, "\nNote that the setting will be ", current?"\sENABLED\s":"\sDISABLED\s", " after map restart."); 66 | } 67 | sprint(e, PRINT_HIGH, "\n"); 68 | }; 69 | 70 | void (entity e, Team_Role * role) PrintRole = { 71 | sprint(e, PRINT_HIGH, "\sRole:\s ", role.name, "\n"); 72 | sprint(e, PRINT_HIGH, "Detpipe Limit: ", ftos(role.detpipe_limit), "\n"); 73 | sprint(e, PRINT_HIGH, "Respawn Delay: ", ftos(role.respawn_delay_time), "\n"); 74 | for(float i = 1; i < 10; i++) { 75 | sprint(e, PRINT_HIGH, CLASSES[i],": \n"); 76 | sprint(e, PRINT_HIGH, "\tGren 1: ", ftos(role.gren1_limits[i]), "\n"); 77 | sprint(e, PRINT_HIGH, "\tGren 2: ", ftos(role.gren2_limits[i]), "\n"); 78 | sprint(e, PRINT_HIGH, "\tClass Limit: ", ftos(role.class_limits[i]), "\n"); 79 | } 80 | sprint(e, PRINT_HIGH, CLASSES[10],": \n"); 81 | sprint(e, PRINT_HIGH, "\tClass Limit: ", ftos(role.class_limits[10]), "\n"); 82 | } 83 | 84 | void () InitTeamRoles = { 85 | Team1_Role = &Role_None; 86 | Team2_Role = &Role_None; 87 | Team3_Role = &Role_None; 88 | Team4_Role = &Role_None; 89 | }; 90 | 91 | void (entity e) PrintTeamRoles = { 92 | sprint(e, PRINT_HIGH, "\sTeam Roles:\s\n"); 93 | if(number_of_teams > 0) { 94 | sprint(e, PRINT_HIGH, GetTeamName(1), ": ", Team1_Role.name, "\n"); 95 | } 96 | if(number_of_teams > 1) { 97 | sprint(e, PRINT_HIGH, GetTeamName(2), ": ", Team2_Role.name, "\n"); 98 | } 99 | if(number_of_teams > 2) { 100 | sprint(e, PRINT_HIGH, GetTeamName(3), ": ", Team3_Role.name, "\n"); 101 | } 102 | if(number_of_teams > 3) { 103 | sprint(e, PRINT_HIGH, GetTeamName(4), ": ", Team4_Role.name, "\n"); 104 | } 105 | }; 106 | 107 | Team_Role* (float tno) GetTeamRole = { 108 | switch (tno) { 109 | case 1: 110 | return Team1_Role; 111 | case 2: 112 | return Team2_Role; 113 | case 3: 114 | return Team3_Role; 115 | case 4: 116 | return Team4_Role; 117 | } 118 | return &Role_None; 119 | }; 120 | 121 | void () item_tfgoal_hidden_touch = { 122 | 123 | if (other.classname != "player") 124 | return; 125 | if (other.health <= 0) 126 | return; 127 | if (cb_prematch) 128 | return; 129 | if (IsFeigned(other)) 130 | return; 131 | if (other == self.owner) 132 | return; 133 | 134 | // check if a wall or something is in the way of the flag 135 | traceline(other.origin, self.origin, TRUE, world); 136 | if (trace_fraction < 1) 137 | return; 138 | 139 | /*if(self.owned_by == other.team_no) { 140 | CenterPrint(other, "Unfortunately, your flag is in another castle\n\nYou are Attacking the other base\n"); 141 | } else { 142 | CenterPrint(other, "You are defending the wrong flag!\n\nGet back to your base quickly\n"); 143 | }*/ 144 | } 145 | 146 | void (entity flag) Quad_HideFlag = { 147 | flag.touch = item_tfgoal_hidden_touch; 148 | setmodel(flag, ""); 149 | setsize(flag, flag.goal_min, flag.goal_max); 150 | }; 151 | 152 | void (entity flag) Quad_UnHideFlag = { 153 | flag.touch = item_tfgoal_touch; 154 | setmodel(flag, flag.mdl); 155 | setsize(flag, flag.goal_min, flag.goal_max); 156 | }; 157 | -------------------------------------------------------------------------------- /ssqc/sniper.qc: -------------------------------------------------------------------------------- 1 | //======================================================== 2 | // Functions for the SNIPER class and associated weaponry 3 | //======================================================== 4 | 5 | void (float zoom_to) Sniper_Zoom = { 6 | self.default_fov = stof(infokey(self, "df")); 7 | self.default_sensitivity = stof(infokey(self, "ds")); 8 | self.zoom_fov = stof(infokey(self, "zf")); 9 | 10 | if (self.default_fov == 0) 11 | return; 12 | 13 | if (zoom_to >= self.default_fov) 14 | Sniper_ZoomReset(self); 15 | else if (zoom_to != self.current_fov) { 16 | if (zoom_to < self.default_fov) 17 | self.zoom_last = zoom_to; 18 | self.current_fov = zoom_to; 19 | stuffcmd(self, "fov "); 20 | stuffcmd(self, ftos(zoom_to)); 21 | stuffcmd(self, "\n"); 22 | 23 | if (self.default_sensitivity > 0) { 24 | stuffcmd(self, "sensitivity "); 25 | stuffcmd(self, ftos(self.default_sensitivity * zoom_to / self.default_fov)); 26 | stuffcmd(self, "\n"); 27 | } 28 | } 29 | }; 30 | 31 | void (entity pl) Sniper_ZoomReset = { 32 | pl.default_sensitivity = stof(infokey(pl, "ds")); 33 | pl.default_fov = stof(infokey(pl, "df")); 34 | 35 | pl.is_zooming = 0; 36 | 37 | if (pl.default_fov > 0) { 38 | pl.current_fov = pl.default_fov; 39 | stuffcmd(pl, "fov "); 40 | stuffcmd(pl, ftos(pl.default_fov)); 41 | stuffcmd(pl, "\n"); 42 | } 43 | 44 | if (pl.default_sensitivity > 0) { 45 | stuffcmd(pl, "sensitivity "); 46 | stuffcmd(pl, ftos(pl.default_sensitivity)); 47 | stuffcmd(pl, "\n"); 48 | } 49 | }; 50 | 51 | void () Sniper_ZoomToggle = { 52 | local float magnification = 0; 53 | local float zoom_to = 0; 54 | 55 | if (self.playerclass != PC_SNIPER) 56 | return; 57 | 58 | self.default_fov = stof(infokey(self, "df")); 59 | self.default_sensitivity = stof(infokey(self, "ds")); 60 | self.zoom_fov = stof(infokey(self, "zf")); 61 | 62 | if (self.default_fov == 0) { 63 | 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"); 64 | return; 65 | } 66 | 67 | if (self.is_zooming) { 68 | self.is_zooming = 0; 69 | zoom_to = self.default_fov; 70 | sprint(self, PRINT_HIGH, "Zoomed out\n"); 71 | } else { 72 | self.is_zooming = 1; 73 | if (self.zoom_last && self.zoom_last < self.default_fov) 74 | zoom_to = self.zoom_last; 75 | else if (self.zoom_fov) 76 | zoom_to = self.zoom_fov; 77 | else 78 | zoom_to = 30; 79 | 80 | magnification = floor(self.default_fov / zoom_to); 81 | sprint(self, PRINT_HIGH, ftos(magnification), "x zoom\n"); 82 | } 83 | 84 | Sniper_Zoom(zoom_to); 85 | }; 86 | 87 | void (float zoom_in) Sniper_ZoomAdjust = { 88 | local float zoom_to = 0; 89 | local float zoom_steps = 0; 90 | 91 | if (self.playerclass != PC_SNIPER || !self.is_zooming) 92 | return; 93 | 94 | zoom_steps = stof(infokey(self, "zs")); 95 | if (!zoom_steps) 96 | zoom_steps = 5; 97 | 98 | if (self.default_fov == 0) { 99 | 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"); 100 | return; 101 | } 102 | 103 | if (zoom_in) 104 | zoom_to = self.current_fov - zoom_steps; 105 | else 106 | zoom_to = self.current_fov + zoom_steps; 107 | 108 | if (zoom_to <= 10) 109 | zoom_to = 10; 110 | else if (zoom_to >= self.default_fov) 111 | zoom_to = self.default_fov; 112 | 113 | self.zoom_last = zoom_to; 114 | 115 | Sniper_Zoom(zoom_to); 116 | }; 117 | 118 | void () SniperSight_Update = { 119 | local vector org; 120 | 121 | if (!(self.owner.tfstate & TFSTATE_AIMING) || 122 | (FO_PlayerCurrentWeapon(self.owner) != WEAP_SNIPER_RIFLE)) { 123 | 124 | self.owner.tfstate &= ~TFSTATE_AIMING; 125 | self.owner.heat = 0; 126 | dremove(self); 127 | return; 128 | } 129 | 130 | makevectors(self.owner.v_angle); 131 | org = self.owner.origin + v_forward * 10; 132 | org_z = self.owner.absmin_z + self.owner.size_z * 0.7; 133 | 134 | traceline(org, org + v_forward * 9192, FALSE, self); 135 | 136 | if (trace_fraction == 1) { 137 | setorigin(self, self.owner.origin); 138 | return; 139 | } 140 | self.angles = vectoangles(v_forward); 141 | setorigin(self, trace_endpos); 142 | self.nextthink = time + 0.1; 143 | }; 144 | 145 | static float sight_send_filter() { 146 | if (other == self.owner) 147 | return FALSE; 148 | 149 | return TRUE; 150 | } 151 | 152 | void () SniperSight_Create = { 153 | local entity sight; 154 | 155 | if (self.has_disconnected == TRUE || (self.tfstate & TFSTATE_RELOADING)) 156 | return; 157 | 158 | self.tfstate = self.tfstate | TFSTATE_AIMING; 159 | 160 | sight = spawn(); 161 | sight.owner = self; 162 | sight.movetype = MOVETYPE_NOCLIP; 163 | sight.solid = SOLID_NOT; 164 | 165 | FO_SetModel(sight, "progs/sight.spr"); 166 | 167 | sight.classname = "timer"; 168 | 169 | setorigin(sight, self.origin); 170 | 171 | sight.think = SniperSight_Update; 172 | sight.nextthink = time + 0.05; 173 | #pragma warning disable F326 174 | if (ClientPred_Enabled(self, CSQC_SNIPER_SIGHT)) 175 | sight.customizeentityforclient = sight_send_filter; 176 | #pragma warning enable F326 177 | }; 178 | -------------------------------------------------------------------------------- /ssqc/spect.qc: -------------------------------------------------------------------------------- 1 | // Spectator functions 2 | // Added Aug11'97 by Zoid 3 | // 4 | // These functions are called from the server if they exist. 5 | // Note that Spectators only have one think since they movement code doesn't 6 | // track them much. Impulse commands work as usual, but don't call 7 | // the regular ImpulseCommand handler in weapons.qc since Spectators don't 8 | // have any weapons and things can explode. 9 | // 10 | // --- Zoid. 11 | 12 | void () SpectatorDisconnect; 13 | void () SpectatorImpulseCommand; 14 | void () SpectatorThink; 15 | void () Admin_Aliases; 16 | 17 | void () SpectatorConnect = { 18 | SetDimensions(TRUE); 19 | 20 | local string st; 21 | 22 | self.playerclass = PC_UNDEFINED; 23 | self.classname = "observer"; 24 | self.flags = 8; 25 | 26 | if (infokey(self,"*login")) 27 | self.login = infokey(self,"*login"); 28 | if (infokey(self,"*admin")) 29 | self.is_admin = stof(infokey(self, "*admin")); 30 | 31 | st = infokey(self, "apw"); 32 | if (st == string_null) 33 | st = infokey(self, "adminpwd"); 34 | if (st) { 35 | Admin_Check(st); 36 | if (self.is_admin) 37 | Admin_Aliases(); 38 | else 39 | self.is_admin = FALSE; 40 | } 41 | 42 | st = infokey(self, "em"); 43 | if (st == string_null) 44 | st = infokey(self, "exec_map"); 45 | 46 | if (st == "on") { 47 | stuffcmd(self, "exec mapdefault.cfg\n"); 48 | stuffcmd(self, "exec spectator.cfg\n"); 49 | stuffcmd(self, "exec "); 50 | stuffcmd(self, mapname); 51 | stuffcmd(self, ".cfg\n"); 52 | } 53 | self.motd = 0; 54 | if(infokeyf(self, INFOKEY_P_CSQCACTIVE)) { 55 | InitAllStatuses(self); 56 | UpdateClientMOTD(self); 57 | UpdateClientTeamScores(self); 58 | UpdateClientPrematch(self, !cb_prematch); 59 | UpdateClient_VoteMap_AddAll(self); 60 | } 61 | 62 | TeamFortress_StartTimers(); 63 | TeamFortress_RemovePracticeSpawn(self); 64 | TeamFortress_SetSpeed(self); 65 | }; 66 | 67 | void () SpectatorDisconnect = { 68 | RemoveAutoIdTimer(); 69 | }; 70 | 71 | void () SpectatorImpulseCommand = { 72 | if (self.impulse == 1) { 73 | self.goalentity = 74 | find(self.goalentity, classname, "info_player_deathmatch"); 75 | if (self.goalentity == world) { 76 | self.goalentity = 77 | find(self.goalentity, classname, "info_player_deathmatch"); 78 | } else { 79 | setorigin(self, self.goalentity.origin); 80 | self.angles = self.goalentity.angles; 81 | self.fixangle = TRUE; 82 | } 83 | } else if (self.impulse == TF_ID) 84 | 85 | CF_Identify(self, self.autoid_type); 86 | 87 | else if (self.impulse == TF_HELP_MAP) 88 | 89 | TeamFortress_HelpMap(); 90 | 91 | else if (self.impulse == TF_STATUS_QUERY) 92 | 93 | TeamFortress_StatusQuery(); 94 | 95 | else if (self.impulse == TF_TEAM_SCORES) 96 | 97 | TeamFortress_TeamShowScores(0); 98 | 99 | if(self.menu_input == Vote_Menu_Map_Input && self.impulse >= 1 && self.impulse <= 10) { 100 | Menu_Input(self.impulse); 101 | } 102 | 103 | if (!self.is_admin) { 104 | self.impulse = 0; 105 | return; 106 | } 107 | 108 | if (self.impulse == TF_ADMIN_CEASEFIRE) 109 | Admin_CeaseFire(); 110 | else if (self.impulse == TF_ADMIN_COUNTPLAYERS) 111 | Admin_CountPlayers(); 112 | else if (self.impulse == TF_ADMIN_CYCLEDEAL) 113 | Admin_CycleDeal(); 114 | else if ((self.impulse == TF_ADMIN_KICK) && (self.admin_mode == 1)) 115 | Admin_DoKick(); 116 | else if ((self.impulse == TF_ADMIN_BAN) && (self.admin_mode == 1)) 117 | Admin_DoBan(); 118 | else if ((self.impulse == TF_ADMIN_NEXT) && (self.admin_mode == 1)) 119 | Admin_CycleDeal(); 120 | else if (self.impulse == TF_ADMIN_LISTIPS) 121 | Admin_ListIPs(); 122 | else if (self.impulse == TF_ADMIN_CLANMODE) 123 | ClanMode(); 124 | else if (self.impulse == TF_ADMIN_QUADMODE) 125 | QuadMode(); 126 | else if (self.impulse == TF_ADMIN_DUELMODE) 127 | DuelMode(); 128 | else if (self.impulse == TF_ADMIN_ADMINMENU) { 129 | self.current_menu_page = 1; 130 | Menu_Admin(); 131 | } 132 | else if (self.impulse == TF_ADMIN_FORCESTARTMATCH) 133 | StartTimer(); 134 | else if (self.impulse == TF_ADMIN_READYSTATUS) 135 | Broadcast_Players_NotReady(); 136 | 137 | else if (self.impulse > 0 && self.impulse <= 10) { 138 | Menu_Input(self.impulse); 139 | } 140 | 141 | self.impulse = 0; 142 | }; 143 | 144 | void () SpectatorThink = { 145 | 146 | self.playerclass = 0; 147 | self.afflicted = 0; 148 | self.teamafflicted = 0; 149 | self.damagegiven = 0; 150 | self.damagetaken = 0; 151 | self.kills = 0; 152 | self.deaths = 0; 153 | self.caps = 0; 154 | self.touches = 0; 155 | self.team_no = 0; 156 | UpdateScoreboardInfo(self); 157 | 158 | if (self.impulse) 159 | SpectatorImpulseCommand(); 160 | 161 | if (time >= self.StatusRefreshTime) { 162 | RefreshStatusBar(self); 163 | } 164 | 165 | Predict_Update(FALSE); 166 | }; 167 | -------------------------------------------------------------------------------- /ssqc/subs.qc: -------------------------------------------------------------------------------- 1 | void (entity Goal, entity AP) DoGoalWork; 2 | void (entity Goal, entity AP) DoGroupWork; 3 | 4 | void FO_SetModel(entity e, string fomdl) 5 | { 6 | //e.dimension_seen = e.dimension_seen - (e.dimension_seen & DMN_FLASH); 7 | e.dimension_seen = DMN_NOFLASH; 8 | setmodel(e, fomdl); 9 | } 10 | 11 | void FO_Sound(entity e, float chan, string samp, float vol, float atten) 12 | { 13 | float olddimens = e.dimension_seen; 14 | // make the sound go to no flash, so it isn't heard by those that are flashed 15 | e.dimension_seen = DMN_NOFLASH; 16 | sound(e, chan, samp, vol, atten); 17 | e.dimension_seen = olddimens; 18 | } 19 | 20 | void () SUB_Null = { 21 | }; 22 | 23 | void (entity et, float f) SUB_Null_pain = { 24 | }; 25 | 26 | void () SUB_Remove = { 27 | dremove(self); 28 | }; 29 | 30 | void () SetMovedir = { 31 | if (self.angles == '0 -1 0') 32 | self.movedir = '0 0 1'; 33 | else if (self.angles == '0 -2 0') 34 | self.movedir = '0 0 -1'; 35 | else { 36 | makevectors(self.angles); 37 | self.movedir = v_forward; 38 | } 39 | self.angles = '0 0 0'; 40 | }; 41 | 42 | void () InitTrigger = { 43 | if (self.angles != '0 0 0') 44 | SetMovedir(); 45 | 46 | self.solid = SOLID_TRIGGER; 47 | setmodel(self, self.model); 48 | self.movetype = MOVETYPE_NONE; 49 | self.modelindex = 0; 50 | self.model = ""; 51 | 52 | }; 53 | 54 | void (vector tdest, float tspeed, void ()func) SUB_CalcMove = { 55 | local vector vdestdelta; 56 | local float len; 57 | local float traveltime; 58 | 59 | if (!tspeed) 60 | objerror("No speed is defined!"); 61 | 62 | self.think1 = func; 63 | self.finaldest = tdest; 64 | self.think = SUB_CalcMoveDone; 65 | 66 | if (tdest == self.origin) { 67 | self.velocity = '0 0 0'; 68 | self.nextthink = self.ltime + 0.1; 69 | return; 70 | } 71 | vdestdelta = tdest - self.origin; 72 | len = vlen(vdestdelta); 73 | traveltime = len / tspeed; 74 | 75 | if (traveltime < 0.03) 76 | traveltime = 0.03; 77 | 78 | self.nextthink = self.ltime + traveltime; 79 | self.velocity = vdestdelta * (1 / traveltime); 80 | }; 81 | 82 | void (entity ent, vector tdest, float tspeed, 83 | void ()func) SUB_CalcMoveEnt = { 84 | local entity stemp; 85 | 86 | stemp = self; 87 | self = ent; 88 | SUB_CalcMove(tdest, tspeed, func); 89 | self = stemp; 90 | 91 | }; 92 | 93 | void () SUB_CalcMoveDone = { 94 | setorigin(self, self.finaldest); 95 | self.velocity = '0 0 0'; 96 | self.nextthink = -1; 97 | if (self.think1) 98 | self.think1(); 99 | }; 100 | 101 | void (vector destangle, float tspeed, void ()func) SUB_CalcAngleMove = { 102 | local vector destdelta; 103 | local float len; 104 | local float traveltime; 105 | 106 | if (!tspeed) 107 | objerror("No speed is defined!"); 108 | 109 | destdelta = destangle - self.angles; 110 | len = vlen(destdelta); 111 | traveltime = len / tspeed; 112 | self.nextthink = self.ltime + traveltime; 113 | self.avelocity = destdelta * (1 / traveltime); 114 | self.think1 = func; 115 | self.finalangle = destangle; 116 | self.think = SUB_CalcAngleMoveDone; 117 | }; 118 | 119 | void (entity ent, vector destangle, float tspeed, 120 | void ()func) SUB_CalcAngleMoveEnt = { 121 | local entity stemp; 122 | 123 | stemp = self; 124 | self = ent; 125 | SUB_CalcAngleMove(destangle, tspeed, func); 126 | self = stemp; 127 | }; 128 | 129 | void () SUB_CalcAngleMoveDone = { 130 | self.angles = self.finalangle; 131 | self.avelocity = '0 0 0'; 132 | self.nextthink = -1; 133 | if (self.think1) 134 | self.think1(); 135 | }; 136 | 137 | void () DelayThink = { 138 | activator = self.enemy; 139 | SUB_UseTargets(); 140 | dremove(self); 141 | }; 142 | 143 | void () SUB_UseTargets = { 144 | local entity t, stemp, otemp, act; 145 | 146 | if (self.dont_do_triggerwork) { 147 | 148 | self.dont_do_triggerwork = FALSE; 149 | return; 150 | } 151 | if (self.delay) { 152 | 153 | t = spawn(); 154 | t.classname = "DelayedUse"; 155 | t.nextthink = time + self.delay; 156 | t.think = DelayThink; 157 | t.enemy = activator; 158 | t.message = self.message; 159 | t.killtarget = self.killtarget; 160 | t.target = self.target; 161 | return; 162 | } 163 | if ((activator.classname == "player") && (self.message != "")) { 164 | 165 | CenterPrint(activator, self.message); 166 | if (!self.noise) 167 | FO_Sound(activator, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM); 168 | } 169 | 170 | if (activator.classname == "player") { 171 | 172 | DoGroupWork(self, activator); 173 | DoGoalWork(self, activator); 174 | } 175 | if (self.killtarget) { 176 | 177 | t = world; 178 | do { 179 | t = find(t, targetname, self.killtarget); 180 | if (!t) 181 | return; 182 | remove(t); 183 | 184 | } while (TRUE); 185 | } 186 | if (self.target) { 187 | 188 | act = activator; 189 | t = world; 190 | do { 191 | t = find(t, targetname, self.target); 192 | if (!t) 193 | return; 194 | stemp = self; 195 | otemp = other; 196 | self = t; 197 | other = stemp; 198 | if (self.use != SUB_Null) { 199 | 200 | if (self.use) 201 | self.use(); 202 | } 203 | self = stemp; 204 | other = otemp; 205 | activator = act; 206 | 207 | } while (TRUE); 208 | } 209 | }; 210 | 211 | void (float normal) SUB_AttackFinished = { 212 | self.cnt = 0; 213 | if (skill < 3) 214 | self.attack_finished = time + normal; 215 | }; 216 | 217 | float (entity targ) visible; 218 | 219 | void (void ()thinkst) SUB_CheckRefire = { 220 | if (skill < 3) 221 | return; 222 | if (self.cnt == 1) 223 | return; 224 | if (!visible(self.enemy)) 225 | return; 226 | self.cnt = 1; 227 | self.think = thinkst; 228 | }; 229 | -------------------------------------------------------------------------------- /ssqc/teamplay.qc: -------------------------------------------------------------------------------- 1 | static void Apply(entity* targets, int tcount, int(entity) filter_fn, 2 | void(entity) apply_fn) { 3 | for (int i = 0; i < tcount; i++) 4 | if (filter_fn(targets[i])) 5 | apply_fn(targets[i]); 6 | } 7 | 8 | static entity EntityOwner(entity e) { 9 | if (e.real_owner != world) 10 | return e.real_owner; 11 | else 12 | return e.owner; 13 | } 14 | 15 | static void ApplyList(entity* targets, int count, 16 | int(entity) player_filter_fn, void(entity) player_apply_fn, 17 | int(entity) other_filter_fn, void(entity) other_apply_fn) { 18 | for (int i = 0; i < count; i++) { 19 | entity e = targets[i]; 20 | 21 | if (e.classname == "player") { 22 | if (player_filter_fn(e)) 23 | player_apply_fn(e); 24 | } else if (EntityOwner(e).classname == "player") { 25 | if (other_filter_fn(e)) 26 | other_apply_fn(e); 27 | } 28 | } 29 | } 30 | 31 | static void ApplyRadius(vector org, float rad, 32 | int(entity) player_filter_fn, void(entity) player_apply_fn, 33 | int(entity) other_filter_fn, void(entity) other_apply_fn) { 34 | int count; 35 | entity* ents = findradius_list(org, rad, count); 36 | ApplyList(ents, count, 37 | player_filter_fn, player_apply_fn, other_filter_fn, other_apply_fn); 38 | } 39 | 40 | void RemovePrimedGrenades(entity player); 41 | 42 | static void EffStrip(entity player) { 43 | player.ammo_cells = 0; 44 | player.ammo_rockets = 0; 45 | player.ammo_shells = 0; 46 | player.ammo_nails = 0; 47 | 48 | player.no_grenades_1 = player.no_grenades_2 = 0; 49 | player.current_slot = player.queue_slot = MakeSlot(4); 50 | 51 | RemovePrimedGrenades(player); 52 | 53 | string msg = "The enemy captured! You are afflicted by the Curse of Isma!"; 54 | stuffcmd(player, "bf\n"); 55 | sprint(player, PRINT_HIGH, msg, "\n"); 56 | centerprint(player, msg); 57 | } 58 | 59 | float IsEngEnt(entity ent); 60 | void RemoveEngEnt(entity bld, float explode); 61 | 62 | static void EffRemove(entity non_player) { 63 | if (non_player.classname == "player") { 64 | printf("ERROR! Tried to remove player [%s]\n", non_player.netname); 65 | return; 66 | } 67 | 68 | pointparticles(particleeffectnum("fo_airblast"), non_player.origin); 69 | if (IsEngEnt(non_player)) 70 | RemoveEngEnt(non_player, FALSE); 71 | else 72 | dremove(non_player); 73 | } 74 | 75 | static int(entity p) FilTeamEQ[] = { 76 | { return p.team_no == 1; }, 77 | { return p.team_no == 2; }, 78 | { return p.team_no == 3; }, 79 | { return p.team_no == 4; }, 80 | }; 81 | 82 | static int(entity p) FilTeamNEQ[] = { 83 | { return p.team_no != 1; }, 84 | { return p.team_no != 2; }, 85 | { return p.team_no != 3; }, 86 | { return p.team_no != 4; }, 87 | }; 88 | 89 | static int(entity p) FilOwnerTeamNEQ[] = { 90 | { return EntityOwner(p).team_no != 1; }, 91 | { return EntityOwner(p).team_no != 2; }, 92 | { return EntityOwner(p).team_no != 3; }, 93 | { return EntityOwner(p).team_no != 4; }, 94 | }; 95 | 96 | void TeamPlay_Cap(vector origin, entity player) { 97 | if (!org_game || !new_balance || !cap_strip) 98 | return; 99 | 100 | ApplyRadius(origin, 1500, 101 | FilTeamNEQ[player.team_no - 1], EffStrip, 102 | FilOwnerTeamNEQ[player.team_no - 1], EffRemove); 103 | } 104 | -------------------------------------------------------------------------------- /ssqc/tforthlp.qc: -------------------------------------------------------------------------------- 1 | //======================================================== 2 | // Functions handling all the help displaying for TeamFortress. 3 | //======================================================== 4 | // 5 | 6 | // so you can select a team (blindly) while reading the MOTD 7 | void (float inp) MOTD_Input = { 8 | Menu_Team_Input(inp); 9 | }; 10 | 11 | void TeamFortress_MOTD() { 12 | if (votemode) 13 | return; 14 | 15 | if (loginRequired && !self.login) 16 | Menu_Login(); 17 | else 18 | Menu_Team(0); 19 | 20 | float csqcactive = infokeyf(self, INFOKEY_P_CSQCACTIVE); 21 | if (csqcactive) { 22 | self.motd = -1; 23 | return; 24 | } 25 | // Below is not really tested, it's an attempt at preserving for posterity. 26 | // Expect potential bugs if we ever reintroduce !CSQC support. 27 | const float ALIAS_OFFSET = 100; 28 | 29 | if (self.motd == 0) { 30 | if ((teamplay != 0) && (self.team_no == 0)) 31 | stuffcmd(self, "color 0\n"); 32 | 33 | sprint(self, PRINT_HIGH, "\nFortressOne ", VER, "\n\n"); 34 | 35 | string st1, st2; 36 | st1 = infokey(world, "motd1"); 37 | if (st1 != string_null) { 38 | st2 = infokey(world, "motd2"); 39 | if (st2 != string_null) { 40 | st1 = strcat(strcat(st1, "\n"), st2); 41 | } 42 | } else { 43 | st1 = "Welcome to FortressOne\n==================================\nwww.github.com/FortressOne"; 44 | } 45 | 46 | sprint(self, PRINT_HIGH, strcat(st1, "\n\n\n")); 47 | if(self.team_no == 0 && !intermission_running) 48 | Status_Menu(self, MOTD_Input, st1); 49 | 50 | self.motd = ALIAS_OFFSET; 51 | } 52 | 53 | float idx = self.motd - ALIAS_OFFSET; 54 | if (idx >= 0 && idx < client_aliases.length) { 55 | TFAlias* alias = &client_aliases[idx]; 56 | 57 | if (alias->nocsqc_cmd) 58 | TeamFortress_AliasString(alias->alias, alias->nocsqc_cmd); 59 | else if (alias->nocsqc_impulse) 60 | TeamFortress_Alias(alias->alias, alias->nocsqc_impulse, 0); 61 | else if (alias->impulse) 62 | TeamFortress_Alias(alias->alias, alias->impulse, 0); 63 | else 64 | TeamFortress_AliasString(alias->alias, alias->cmd); 65 | } 66 | 67 | if (idx < client_aliases.length) 68 | self.motd += 1; 69 | else 70 | self.motd = -1; 71 | }; 72 | 73 | void () TeamFortress_HelpMap = { 74 | local entity te; 75 | 76 | te = find(world, classname, "info_tfdetect"); 77 | if (te) { 78 | if (te.non_team_broadcast != string_null) { 79 | sprint(self, PRINT_HIGH, te.non_team_broadcast); 80 | return; 81 | } 82 | } 83 | sprint(self, PRINT_HIGH, "There is no help for this map\n"); 84 | }; 85 | -------------------------------------------------------------------------------- /ssqc/time.qc: -------------------------------------------------------------------------------- 1 | float remote_client_time() { 2 | float adj_ping = max(self.client_ping - SERVER_FRAME_MS, 0); 3 | float offset = min(adj_ping, fo_config.max_rewind_ms); 4 | 5 | float target = self.client_time - offset / 1000.0; 6 | target = max(target, self.last_remote_client_time); // prevent jitter 7 | self.last_remote_client_time = target; 8 | 9 | return target; 10 | } 11 | 12 | // Note: Delta has jitter of up to ~frame 13 | inline float client_to_global_time(float ctime) { 14 | return time + (ctime - self.client_time); 15 | } 16 | 17 | inline float global_to_client_time(float gtime) { 18 | return self.client_time + (gtime - time); 19 | } 20 | 21 | // Ping corrected `time`. 22 | float remote_time() { 23 | return client_to_global_time(remote_client_time()); 24 | } 25 | 26 | float bounded_remote_time(float dt) { 27 | return max(time - dt, remote_time()); 28 | } 29 | 30 | void FO_CheckClientThink() { 31 | if (self.client_nextthink > 0 && self.client_time >= self.client_nextthink) { 32 | float held_client_time = self.client_time; 33 | 34 | self.client_time = self.client_nextthink; 35 | self.client_nextthink = 0; 36 | self.client_think(); 37 | 38 | self.client_time = held_client_time; 39 | } 40 | } 41 | 42 | void FO_UpdateClientTime() { 43 | self.client_time += frametime; 44 | 45 | self.client_ping = infokeyf(self, INFOKEY_P_PING); 46 | } 47 | -------------------------------------------------------------------------------- /version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -f VERSION ]; then 4 | ver=$(cat VERSION) 5 | rev=$(sed -e 's/^r\([0-9]\+\).*$/\1/' VERSION) 6 | elif [ -x "$(command -v git)" -a -d ".git" ]; then 7 | rev=$(git rev-parse --short HEAD) 8 | ver="r$rev~$(git rev-parse --short HEAD)" 9 | else 10 | echo "WARNING: Couldn't detect ezQuake version." >&2 11 | ver="r666" 12 | rev="666" 13 | fi 14 | 15 | case $1 in 16 | --version|-v) 17 | echo $ver;; 18 | --revision|-r) 19 | echo $rev;; 20 | *) 21 | echo $ver 22 | echo $rev;; 23 | esac 24 | 25 | --------------------------------------------------------------------------------