From: Mario Date: Mon, 11 Jun 2018 07:48:10 +0000 (+1000) Subject: Merge branch 'master' into Lyberta/PrintMove X-Git-Tag: xonotic-v0.8.5~2074^2~1 X-Git-Url: https://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=commitdiff_plain;h=0076d3f631e54b908b7506883c75c6d28f6b9505;hp=b093c2ea2c367cb9bb4ce2c0468346080938270c Merge branch 'master' into Lyberta/PrintMove --- diff --git a/gamemodes-server.cfg b/gamemodes-server.cfg index 6790a3b4f..7319cba4e 100644 --- a/gamemodes-server.cfg +++ b/gamemodes-server.cfg @@ -308,6 +308,7 @@ exec ctfscoring-samual.cfg set g_cts 0 "CTS: complete the stage" set g_cts_selfdamage 1 "0 = disable all selfdamage and falldamage in cts" set g_cts_finish_kill_delay 10 "prevent cheating by running back to the start line, and starting out with more speed than otherwise possible" +set g_cts_send_rankings_cnt 15 "send this number of map records to clients" // ========================== diff --git a/mutators.cfg b/mutators.cfg index c997a807f..a4c8144fc 100644 --- a/mutators.cfg +++ b/mutators.cfg @@ -55,6 +55,7 @@ set g_instagib_friendlypush 1 "allow pushing teammates with the vaporizer primar // overkill // ========== set g_overkill 0 "internal cvar, to enable overkill, use `exec ruleset-overkill.cfg`" +set g_overkill_weapons 0 "Whether to enable overkill weapons outside of overkill ruleset." set g_overkill_powerups_replace 1 set g_overkill_itemwaypoints 1 diff --git a/qcsrc/client/_mod.inc b/qcsrc/client/_mod.inc index 240e07a4a..aa20961a2 100644 --- a/qcsrc/client/_mod.inc +++ b/qcsrc/client/_mod.inc @@ -9,7 +9,6 @@ #include #include #include -#include #include #include diff --git a/qcsrc/client/_mod.qh b/qcsrc/client/_mod.qh index 10482caaa..ecfa4ee1a 100644 --- a/qcsrc/client/_mod.qh +++ b/qcsrc/client/_mod.qh @@ -9,7 +9,6 @@ #include #include #include -#include #include #include diff --git a/qcsrc/client/announcer.qc b/qcsrc/client/announcer.qc index 62b732bec..bcbe72446 100644 --- a/qcsrc/client/announcer.qc +++ b/qcsrc/client/announcer.qc @@ -1,6 +1,6 @@ #include "announcer.qh" -#include "mutators/events.qh" +#include #include #include diff --git a/qcsrc/client/commands/cl_cmd.qc b/qcsrc/client/commands/cl_cmd.qc index 8eea240d4..5a53be8e7 100644 --- a/qcsrc/client/commands/cl_cmd.qc +++ b/qcsrc/client/commands/cl_cmd.qc @@ -14,7 +14,7 @@ #include "../mapvoting.qh" #include "../miscfunctions.qh" -#include "../mutators/events.qh" +#include #include diff --git a/qcsrc/client/csqcmodel_hooks.qc b/qcsrc/client/csqcmodel_hooks.qc index 6499a683e..37e18bd8e 100644 --- a/qcsrc/client/csqcmodel_hooks.qc +++ b/qcsrc/client/csqcmodel_hooks.qc @@ -2,7 +2,7 @@ #include "autocvars.qh" #include "csqcmodel_hooks.qh" #include "miscfunctions.qh" -#include "mutators/events.qh" +#include #include "player_skeleton.qh" #include "weapons/projectile.qh" #include diff --git a/qcsrc/client/hud/hud.qc b/qcsrc/client/hud/hud.qc index 01799cacc..55d7ffeaa 100644 --- a/qcsrc/client/hud/hud.qc +++ b/qcsrc/client/hud/hud.qc @@ -16,7 +16,7 @@ #include #include #include -#include // TODO: remove +#include /* diff --git a/qcsrc/client/hud/panel.qh b/qcsrc/client/hud/panel.qh index 66ca0772b..1ea23ae2e 100644 --- a/qcsrc/client/hud/panel.qh +++ b/qcsrc/client/hud/panel.qh @@ -2,4 +2,4 @@ #include "hud.qh" #include "hud_config.qh" -#include +#include diff --git a/qcsrc/client/hud/panel/modicons.qc b/qcsrc/client/hud/panel/modicons.qc index 0bbcb4148..89b8a8c18 100644 --- a/qcsrc/client/hud/panel/modicons.qc +++ b/qcsrc/client/hud/panel/modicons.qc @@ -5,7 +5,7 @@ #include #include #include -#include // TODO: remove +#include // Mod icons (#10) diff --git a/qcsrc/client/main.qc b/qcsrc/client/main.qc index 37027d25c..efb1fa204 100644 --- a/qcsrc/client/main.qc +++ b/qcsrc/client/main.qc @@ -9,12 +9,11 @@ #include #include "hud/_mod.qh" #include "mapvoting.qh" -#include "mutators/events.qh" +#include #include "hud/panel/scoreboard.qh" #include "hud/panel/quickmenu.qh" #include "shownames.qh" #include -#include "wall.qh" #include "weapons/projectile.qh" #include #include @@ -24,7 +23,7 @@ #include #include #include -#include +#include #include #include #include @@ -1144,6 +1143,9 @@ NET_HANDLE(TE_CSQC_RACE, bool isNew) strcpy(race_speedaward_alltimebest_holder, ReadString()); strcpy(race_speedaward_alltimebest_unit, GetSpeedUnit(autocvar_hud_panel_physics_speed_unit)); break; + case RACE_NET_RANKINGS_CNT: + RANKINGS_DISPLAY_CNT = ReadByte(); + break; case RACE_NET_SERVER_RANKINGS: float prevpos, del; int pos = ReadShort(); @@ -1153,13 +1155,14 @@ NET_HANDLE(TE_CSQC_RACE, bool isNew) // move other rankings out of the way int i; if (prevpos) { - for (i=prevpos-1;i>pos-1;--i) { + int m = min(prevpos, RANKINGS_DISPLAY_CNT); + for (i=m-1; i>pos-1; --i) { grecordtime[i] = grecordtime[i-1]; strcpy(grecordholder[i], grecordholder[i-1]); } } else if (del) { // a record has been deleted by the admin - for (i=pos-1; i<= RANKINGS_CNT-1; ++i) { - if (i == RANKINGS_CNT-1) { // clear out last record + for (i=pos-1; i<= RANKINGS_DISPLAY_CNT-1; ++i) { + if (i == RANKINGS_DISPLAY_CNT-1) { // clear out last record grecordtime[i] = 0; strfree(grecordholder[i]); } @@ -1169,12 +1172,18 @@ NET_HANDLE(TE_CSQC_RACE, bool isNew) } } } else { // player has no ranked record yet - for (i=RANKINGS_CNT-1;i>pos-1;--i) { + for (i=RANKINGS_DISPLAY_CNT-1;i>pos-1;--i) { grecordtime[i] = grecordtime[i-1]; strcpy(grecordholder[i], grecordholder[i-1]); } } + if (grecordtime[RANKINGS_DISPLAY_CNT]) { + // kick off the player who fell from the last displayed position + grecordtime[RANKINGS_DISPLAY_CNT] = 0; + strfree(grecordholder[RANKINGS_DISPLAY_CNT]); + } + // store new ranking strcpy(grecordholder[pos-1], ReadString()); grecordtime[pos-1] = ReadInt24_t(); diff --git a/qcsrc/client/main.qh b/qcsrc/client/main.qh index a2f4d18bb..70189b3a2 100644 --- a/qcsrc/client/main.qh +++ b/qcsrc/client/main.qh @@ -24,6 +24,7 @@ void LoadMenuSkinValues(); vector hud_fontsize; float RANKINGS_RECEIVED_CNT; +float RANKINGS_DISPLAY_CNT; string grecordholder[RANKINGS_CNT]; float grecordtime[RANKINGS_CNT]; diff --git a/qcsrc/client/player_skeleton.qc b/qcsrc/client/player_skeleton.qc index 220b9c612..bb1b6f919 100644 --- a/qcsrc/client/player_skeleton.qc +++ b/qcsrc/client/player_skeleton.qc @@ -2,7 +2,7 @@ #include #include -#include "mutators/events.qh" +#include #include "../lib/csqcmodel/cl_player.qh" #include "../lib/warpzone/anglestransform.qh" diff --git a/qcsrc/client/view.qc b/qcsrc/client/view.qc index f2196cd1c..a1b53fb82 100644 --- a/qcsrc/client/view.qc +++ b/qcsrc/client/view.qc @@ -9,7 +9,7 @@ #include "hud/panel/scoreboard.qh" #include "hud/panel/quickmenu.qh" -#include "mutators/events.qh" +#include #include #include @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include #include @@ -32,7 +32,7 @@ #include #include #include -#include +#include #include #include diff --git a/qcsrc/client/wall.qc b/qcsrc/client/wall.qc deleted file mode 100644 index 600bf5fa8..000000000 --- a/qcsrc/client/wall.qc +++ /dev/null @@ -1,226 +0,0 @@ -#include "wall.qh" - -#include "autocvars.qh" -#include "main.qh" -#include "bgmscript.qh" - - -#include "../lib/csqcmodel/interpolate.qh" - -.float alpha; -.float scale; -.vector movedir; - -void Ent_Wall_PreDraw(entity this) -{ - if (this.inactive) - { - this.alpha = 0; - } - else - { - vector org = getpropertyvec(VF_ORIGIN); - if(!checkpvs(org, this)) - this.alpha = 0; - else if(this.fade_start || this.fade_end) { - vector offset = '0 0 0'; - offset_z = this.fade_vertical_offset; - float player_dist = vlen(org - this.origin - 0.5 * (this.mins + this.maxs) + offset); - if (this.fade_end == this.fade_start) - { - if (player_dist >= this.fade_start) - this.alpha = 0; - else - this.alpha = 1; - } - else - { - this.alpha = (this.alpha_min + this.alpha_max * bound(0, - (this.fade_end - player_dist) - / (this.fade_end - this.fade_start), 1)) / 100.0; - } - } - else - { - this.alpha = 1; - } - } - if(this.alpha <= 0) - this.drawmask = 0; - else - this.drawmask = MASK_NORMAL; -} - -void Ent_Wall_Draw(entity this) -{ - float f; - var .vector fld; - - if(this.bgmscriptangular) - fld = angles; - else - fld = origin; - this.(fld) = this.saved; - - if(this.lodmodelindex1) - { - if(autocvar_cl_modeldetailreduction <= 0) - { - if(this.lodmodelindex2 && autocvar_cl_modeldetailreduction <= -2) - this.modelindex = this.lodmodelindex2; - else if(autocvar_cl_modeldetailreduction <= -1) - this.modelindex = this.lodmodelindex1; - else - this.modelindex = this.lodmodelindex0; - } - else - { - float distance = vlen(NearestPointOnBox(this, view_origin) - view_origin); - f = (distance * current_viewzoom + 100.0) * autocvar_cl_modeldetailreduction; - f *= 1.0 / bound(0.01, view_quality, 1); - if(this.lodmodelindex2 && f > this.loddistance2) - this.modelindex = this.lodmodelindex2; - else if(f > this.loddistance1) - this.modelindex = this.lodmodelindex1; - else - this.modelindex = this.lodmodelindex0; - } - } - - InterpolateOrigin_Do(this); - - this.saved = this.(fld); - - f = doBGMScript(this); - if(f >= 0) - { - if(this.lip < 0) // < 0: alpha goes from 1 to 1-|lip| when toggled (toggling subtracts lip) - this.alpha = 1 + this.lip * f; - else // > 0: alpha goes from 1-|lip| to 1 when toggled (toggling adds lip) - this.alpha = 1 - this.lip * (1 - f); - this.(fld) = this.(fld) + this.movedir * f; - } - else - this.alpha = 1; - - if(this.alpha >= ALPHA_MIN_VISIBLE) - this.drawmask = MASK_NORMAL; - else - this.drawmask = 0; -} - -void Ent_Wall_Remove(entity this) -{ - strfree(this.bgmscript); -} - -NET_HANDLE(ENT_CLIENT_WALL, bool isnew) -{ - int f; - var .vector fld; - - InterpolateOrigin_Undo(this); - this.iflags = IFLAG_ANGLES | IFLAG_ORIGIN; - - if(this.bgmscriptangular) - fld = angles; - else - fld = origin; - this.(fld) = this.saved; - - f = ReadByte(); - - if(f & 1) - { - if(f & 0x40) - this.colormap = ReadShort(); - else - this.colormap = 0; - this.skin = ReadByte(); - } - - if(f & 2) - { - this.origin = ReadVector(); - setorigin(this, this.origin); - } - - if(f & 4) - { - if(f & 0x10) - { - this.angles_x = ReadAngle(); - this.angles_y = ReadAngle(); - this.angles_z = ReadAngle(); - } - else - this.angles = '0 0 0'; - } - - if(f & 8) - { - if(f & 0x80) - { - this.lodmodelindex0 = ReadShort(); - this.loddistance1 = ReadShort(); - this.lodmodelindex1 = ReadShort(); - this.loddistance2 = ReadShort(); - this.lodmodelindex2 = ReadShort(); - } - else - { - this.modelindex = ReadShort(); - this.loddistance1 = 0; - this.loddistance2 = 0; - } - this.solid = ReadByte(); - this.scale = ReadShort() / 256.0; - if(f & 0x20) - { - this.mins = ReadVector(); - this.maxs = ReadVector(); - } - else - this.mins = this.maxs = '0 0 0'; - setsize(this, this.mins, this.maxs); - - string s = ReadString(); - if(substring(s, 0, 1) == "<") - { - strcpy(this.bgmscript, substring(s, 1, -1)); - this.bgmscriptangular = 1; - } - else - { - strcpy(this.bgmscript, s); - this.bgmscriptangular = 0; - } - if(this.bgmscript != "") - { - this.bgmscriptattack = ReadByte() / 64.0; - this.bgmscriptdecay = ReadByte() / 64.0; - this.bgmscriptsustain = ReadByte() / 255.0; - this.bgmscriptrelease = ReadByte() / 64.0; - this.movedir = ReadVector(); - this.lip = ReadByte() / 255.0; - } - this.fade_start = ReadByte(); - this.fade_end = ReadByte(); - this.alpha_max = ReadByte(); - this.alpha_min = ReadByte(); - this.inactive = ReadByte(); - this.fade_vertical_offset = ReadShort(); - BGMScript_InitEntity(this); - } - - return = true; - - InterpolateOrigin_Note(this); - - this.saved = this.(fld); - - this.entremove = Ent_Wall_Remove; - this.draw = Ent_Wall_Draw; - if (isnew) IL_PUSH(g_drawables, this); - setpredraw(this, Ent_Wall_PreDraw); -} diff --git a/qcsrc/client/wall.qh b/qcsrc/client/wall.qh deleted file mode 100644 index e55bc489b..000000000 --- a/qcsrc/client/wall.qh +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -entityclass(Wall); -classfield(Wall) .float lip; -classfield(Wall) .float bgmscriptangular; -classfield(Wall) .int lodmodelindex0, lodmodelindex1, lodmodelindex2; -classfield(Wall) .float loddistance1, loddistance2; -classfield(Wall) .vector saved; - -// Needed for interactive clientwalls -.float inactive; // Clientwall disappears when inactive -.float alpha_max, alpha_min; -// If fade_start > fade_end, fadeout will be inverted -// fade_vertical_offset is a vertival offset for player position -.float fade_start, fade_end, fade_vertical_offset; -.float default_solid; - -void Ent_Wall_Draw(entity this); - -void Ent_Wall_Remove(entity this); diff --git a/qcsrc/client/weapons/projectile.qc b/qcsrc/client/weapons/projectile.qc index 089c7df11..444fb2743 100644 --- a/qcsrc/client/weapons/projectile.qc +++ b/qcsrc/client/weapons/projectile.qc @@ -3,7 +3,7 @@ #include "../autocvars.qh" #include "../defs.qh" #include "../main.qh" -#include "../mutators/events.qh" +#include #include #include diff --git a/qcsrc/common/_all.inc b/qcsrc/common/_all.inc index 32dbf5208..9d7c68a8e 100644 --- a/qcsrc/common/_all.inc +++ b/qcsrc/common/_all.inc @@ -22,7 +22,7 @@ noref float autocvar_net_connecttimeout = 30; #ifdef GAMEQC #include "physics/all.inc" -#include "triggers/include.qc" +#include "mapobjects/_mod.inc" #include "viewloc.qc" #endif diff --git a/qcsrc/common/constants.qh b/qcsrc/common/constants.qh index 18ea6d7d1..675b75c0a 100644 --- a/qcsrc/common/constants.qh +++ b/qcsrc/common/constants.qh @@ -1,6 +1,6 @@ #pragma once -const int RANKINGS_CNT = 15; +const int RANKINGS_CNT = 99; /////////////////////////// // keys pressed diff --git a/qcsrc/common/effects/qc/damageeffects.qh b/qcsrc/common/effects/qc/damageeffects.qh index 2a1d587ca..68b43b176 100644 --- a/qcsrc/common/effects/qc/damageeffects.qh +++ b/qcsrc/common/effects/qc/damageeffects.qh @@ -3,7 +3,7 @@ #ifdef CSQC #include #include -#include +#include #include #include #endif diff --git a/qcsrc/common/ent_cs.qc b/qcsrc/common/ent_cs.qc index 0eeddc349..bbca691ad 100644 --- a/qcsrc/common/ent_cs.qc +++ b/qcsrc/common/ent_cs.qc @@ -1,4 +1,5 @@ #include "ent_cs.qh" +#include REGISTRY(EntCSProps, BITS(16) - 1) #define EntCSProps_from(i) _EntCSProps_from(i, NULL) diff --git a/qcsrc/common/gamemodes/gamemode/_mod.inc b/qcsrc/common/gamemodes/gamemode/_mod.inc index 2fc2c4046..c4cd002c7 100644 --- a/qcsrc/common/gamemodes/gamemode/_mod.inc +++ b/qcsrc/common/gamemodes/gamemode/_mod.inc @@ -1,4 +1,17 @@ // generated file; do not modify +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include diff --git a/qcsrc/common/gamemodes/gamemode/_mod.qh b/qcsrc/common/gamemodes/gamemode/_mod.qh index d79957012..d7c1aa66c 100644 --- a/qcsrc/common/gamemodes/gamemode/_mod.qh +++ b/qcsrc/common/gamemodes/gamemode/_mod.qh @@ -1,4 +1,17 @@ // generated file; do not modify +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include diff --git a/qcsrc/common/gamemodes/gamemode/assault/_mod.inc b/qcsrc/common/gamemodes/gamemode/assault/_mod.inc new file mode 100644 index 000000000..1deb03156 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/assault/_mod.inc @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/assault/_mod.qh b/qcsrc/common/gamemodes/gamemode/assault/_mod.qh new file mode 100644 index 000000000..38b426d4a --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/assault/_mod.qh @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/assault/assault.qc b/qcsrc/common/gamemodes/gamemode/assault/assault.qc new file mode 100644 index 000000000..ba3d996d1 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/assault/assault.qc @@ -0,0 +1,628 @@ +#include "assault.qh" + +// TODO: split into sv_assault +#ifdef SVQC +.entity sprite; +#define AS_ROUND_DELAY 5 + +IntrusiveList g_assault_destructibles; +IntrusiveList g_assault_objectivedecreasers; +IntrusiveList g_assault_objectives; +STATIC_INIT(g_assault) +{ + g_assault_destructibles = IL_NEW(); + g_assault_objectivedecreasers = IL_NEW(); + g_assault_objectives = IL_NEW(); +} + +// random functions +void assault_objective_use(entity this, entity actor, entity trigger) +{ + // activate objective + this.health = 100; + //print("^2Activated objective ", this.targetname, "=", etos(this), "\n"); + //print("Activator is ", actor.classname, "\n"); + + IL_EACH(g_assault_objectivedecreasers, it.target == this.targetname, + { + target_objective_decrease_activate(it); + }); +} + +vector target_objective_spawn_evalfunc(entity this, entity player, entity spot, vector current) +{ + if(this.health < 0 || this.health >= ASSAULT_VALUE_INACTIVE) + return '-1 0 0'; + return current; +} + +// reset this objective. Used when spawning an objective +// and when a new round starts +void assault_objective_reset(entity this) +{ + this.health = ASSAULT_VALUE_INACTIVE; +} + +// decrease the health of targeted objectives +void assault_objective_decrease_use(entity this, entity actor, entity trigger) +{ + if(actor.team != assault_attacker_team) + { + // wrong team triggered decrease + return; + } + + if(trigger.assault_sprite) + { + WaypointSprite_Disown(trigger.assault_sprite, waypointsprite_deadlifetime); + if(trigger.classname == "func_assault_destructible") + trigger.sprite = NULL; // TODO: just unsetting it?! + } + else + return; // already activated! cannot activate again! + + if(this.enemy.health < ASSAULT_VALUE_INACTIVE) + { + if(this.enemy.health - this.dmg > 0.5) + { + GameRules_scoring_add_team(actor, SCORE, this.dmg); + this.enemy.health = this.enemy.health - this.dmg; + } + else + { + GameRules_scoring_add_team(actor, SCORE, this.enemy.health); + GameRules_scoring_add_team(actor, ASSAULT_OBJECTIVES, 1); + this.enemy.health = -1; + + if(this.enemy.message) + FOREACH_CLIENT(IS_PLAYER(it), { centerprint(it, this.enemy.message); }); + + SUB_UseTargets(this.enemy, this, trigger); + } + } +} + +void assault_setenemytoobjective(entity this) +{ + IL_EACH(g_assault_objectives, it.targetname == this.target, + { + if(this.enemy == NULL) + this.enemy = it; + else + objerror(this, "more than one objective as target - fix the map!"); + break; + }); + + if(this.enemy == NULL) + objerror(this, "no objective as target - fix the map!"); +} + +bool assault_decreaser_sprite_visible(entity this, entity player, entity view) +{ + if(this.assault_decreaser.enemy.health >= ASSAULT_VALUE_INACTIVE) + return false; + + return true; +} + +void target_objective_decrease_activate(entity this) +{ + entity spr; + this.owner = NULL; + FOREACH_ENTITY_STRING(target, this.targetname, + { + if(it.assault_sprite != NULL) + { + WaypointSprite_Disown(it.assault_sprite, waypointsprite_deadlifetime); + if(it.classname == "func_assault_destructible") + it.sprite = NULL; // TODO: just unsetting it?! + } + + spr = WaypointSprite_SpawnFixed(WP_AssaultDefend, 0.5 * (it.absmin + it.absmax), it, assault_sprite, RADARICON_OBJECTIVE); + spr.assault_decreaser = this; + spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible; + spr.classname = "sprite_waypoint"; + WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY); + if(it.classname == "func_assault_destructible") + { + WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy); + WaypointSprite_UpdateMaxHealth(spr, it.max_health); + WaypointSprite_UpdateHealth(spr, it.health); + it.sprite = spr; + } + else + WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultPush, WP_AssaultPush); + }); +} + +void target_objective_decrease_findtarget(entity this) +{ + assault_setenemytoobjective(this); +} + +void target_assault_roundend_reset(entity this) +{ + //print("round end reset\n"); + ++this.cnt; // up round counter + this.winning = false; // up round +} + +void target_assault_roundend_use(entity this, entity actor, entity trigger) +{ + this.winning = 1; // round has been won by attackers +} + +void assault_roundstart_use(entity this, entity actor, entity trigger) +{ + SUB_UseTargets(this, this, trigger); + + //(Re)spawn all turrets + IL_EACH(g_turrets, true, + { + // Swap turret teams + if(it.team == NUM_TEAM_1) + it.team = NUM_TEAM_2; + else + it.team = NUM_TEAM_1; + + // Doubles as teamchange + turret_respawn(it); + }); +} +void assault_roundstart_use_this(entity this) +{ + assault_roundstart_use(this, NULL, NULL); +} + +void assault_wall_think(entity this) +{ + if(this.enemy.health < 0) + { + this.model = ""; + this.solid = SOLID_NOT; + } + else + { + this.model = this.mdl; + this.solid = SOLID_BSP; + } + + this.nextthink = time + 0.2; +} + +// trigger new round +// reset objectives, toggle spawnpoints, reset triggers, ... +void assault_new_round(entity this) +{ + //bprint("ASSAULT: new round\n"); + + // up round counter + this.winning = this.winning + 1; + + // swap attacker/defender roles + if(assault_attacker_team == NUM_TEAM_1) + assault_attacker_team = NUM_TEAM_2; + else + assault_attacker_team = NUM_TEAM_1; + + IL_EACH(g_saved_team, !IS_CLIENT(it), + { + if(it.team_saved == NUM_TEAM_1) + it.team_saved = NUM_TEAM_2; + else if(it.team_saved == NUM_TEAM_2) + it.team_saved = NUM_TEAM_1; + }); + + // reset the level with a countdown + cvar_set("timelimit", ftos(ceil(time - AS_ROUND_DELAY - game_starttime) / 60)); + ReadyRestart_force(); // sets game_starttime +} + +entity as_round; +.entity ent_winning; +void as_round_think() +{ + game_stopped = false; + assault_new_round(as_round.ent_winning); + delete(as_round); + as_round = NULL; +} + +// Assault winning condition: If the attackers triggered a round end (by fulfilling all objectives) +// they win. Otherwise the defending team wins once the timelimit passes. +int WinningCondition_Assault() +{ + if(as_round) + return WINNING_NO; + + WinningConditionHelper(NULL); // set worldstatus + + int status = WINNING_NO; + // as the timelimit has not yet passed just assume the defending team will win + if(assault_attacker_team == NUM_TEAM_1) + { + SetWinners(team, NUM_TEAM_2); + } + else + { + SetWinners(team, NUM_TEAM_1); + } + + entity ent; + ent = find(NULL, classname, "target_assault_roundend"); + if(ent) + { + if(ent.winning) // round end has been triggered by attacking team + { + bprint("Assault: round completed.\n"); + SetWinners(team, assault_attacker_team); + + TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 666 - TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 0)); + + if(ent.cnt == 1 || autocvar_g_campaign) // this was the second round + { + status = WINNING_YES; + } + else + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ASSAULT_OBJ_DESTROYED, ceil(time - game_starttime)); + as_round = new(as_round); + as_round.think = as_round_think; + as_round.ent_winning = ent; + as_round.nextthink = time + AS_ROUND_DELAY; + game_stopped = true; + + // make sure timelimit isn't hit while the game is blocked + if(autocvar_timelimit > 0) + if(time + AS_ROUND_DELAY >= game_starttime + autocvar_timelimit * 60) + cvar_set("timelimit", ftos(autocvar_timelimit + AS_ROUND_DELAY / 60)); + } + } + } + + return status; +} + +// spawnfuncs +spawnfunc(info_player_attacker) +{ + if (!g_assault) { delete(this); return; } + + this.team = NUM_TEAM_1; // red, gets swapped every round + spawnfunc_info_player_deathmatch(this); +} + +spawnfunc(info_player_defender) +{ + if (!g_assault) { delete(this); return; } + + this.team = NUM_TEAM_2; // blue, gets swapped every round + spawnfunc_info_player_deathmatch(this); +} + +spawnfunc(target_objective) +{ + if (!g_assault) { delete(this); return; } + + this.classname = "target_objective"; + IL_PUSH(g_assault_objectives, this); + this.use = assault_objective_use; + this.reset = assault_objective_reset; + this.reset(this); + this.spawn_evalfunc = target_objective_spawn_evalfunc; +} + +spawnfunc(target_objective_decrease) +{ + if (!g_assault) { delete(this); return; } + + this.classname = "target_objective_decrease"; + IL_PUSH(g_assault_objectivedecreasers, this); + + if(!this.dmg) + this.dmg = 101; + + this.use = assault_objective_decrease_use; + this.health = ASSAULT_VALUE_INACTIVE; + this.max_health = ASSAULT_VALUE_INACTIVE; + this.enemy = NULL; + + InitializeEntity(this, target_objective_decrease_findtarget, INITPRIO_FINDTARGET); +} + +// destructible walls that can be used to trigger target_objective_decrease +spawnfunc(func_breakable); +spawnfunc(func_assault_destructible) +{ + if (!g_assault) { delete(this); return; } + + this.spawnflags = 3; + this.classname = "func_assault_destructible"; + IL_PUSH(g_assault_destructibles, this); + + if(assault_attacker_team == NUM_TEAM_1) + this.team = NUM_TEAM_2; + else + this.team = NUM_TEAM_1; + + spawnfunc_func_breakable(this); +} + +spawnfunc(func_assault_wall) +{ + if (!g_assault) { delete(this); return; } + + this.classname = "func_assault_wall"; + this.mdl = this.model; + _setmodel(this, this.mdl); + this.solid = SOLID_BSP; + setthink(this, assault_wall_think); + this.nextthink = time; + InitializeEntity(this, assault_setenemytoobjective, INITPRIO_FINDTARGET); +} + +spawnfunc(target_assault_roundend) +{ + if (!g_assault) { delete(this); return; } + + this.winning = 0; // round not yet won by attackers + this.classname = "target_assault_roundend"; + this.use = target_assault_roundend_use; + this.cnt = 0; // first round + this.reset = target_assault_roundend_reset; +} + +spawnfunc(target_assault_roundstart) +{ + if (!g_assault) { delete(this); return; } + + assault_attacker_team = NUM_TEAM_1; + this.classname = "target_assault_roundstart"; + this.use = assault_roundstart_use; + this.reset2 = assault_roundstart_use_this; + InitializeEntity(this, assault_roundstart_use_this, INITPRIO_FINDTARGET); +} + +// legacy bot code +void havocbot_goalrating_ast_targets(entity this, float ratingscale) +{ + IL_EACH(g_assault_destructibles, it.bot_attack, + { + if (it.target == "") + continue; + + bool found = false; + entity destr = it; + IL_EACH(g_assault_objectivedecreasers, it.targetname == destr.target, + { + if(it.enemy.health > 0 && it.enemy.health < ASSAULT_VALUE_INACTIVE) + { + found = true; + break; + } + }); + + if(!found) + continue; + + vector p = 0.5 * (it.absmin + it.absmax); + + // Find and rate waypoints around it + found = false; + entity best = NULL; + float bestvalue = 99999999999; + entity des = it; + for(float radius = 0; radius < 1500 && !found; radius += 500) + { + FOREACH_ENTITY_RADIUS(p, radius, it.classname == "waypoint" && !(it.wpflags & WAYPOINTFLAG_GENERATED), + { + if(checkpvs(it.origin, des)) + { + found = true; + if(it.cnt < bestvalue) + { + best = it; + bestvalue = it.cnt; + } + } + }); + } + + if(best) + { + /// dprint("waypoints around target were found\n"); + // te_lightning2(NULL, '0 0 0', best.origin); + // te_knightspike(best.origin); + + navigation_routerating(this, best, ratingscale, 4000); + best.cnt += 1; + + this.havocbot_attack_time = 0; + + if(checkpvs(this.origin + this.view_ofs, it)) + if(checkpvs(this.origin + this.view_ofs, best)) + { + // dprint("increasing attack time for this target\n"); + this.havocbot_attack_time = time + 2; + } + } + }); +} + +void havocbot_role_ast_offense(entity this) +{ + if(IS_DEAD(this)) + { + this.havocbot_attack_time = 0; + havocbot_ast_reset_role(this); + return; + } + + // Set the role timeout if necessary + if (!this.havocbot_role_timeout) + this.havocbot_role_timeout = time + 120; + + if (time > this.havocbot_role_timeout) + { + havocbot_ast_reset_role(this); + return; + } + + if(this.havocbot_attack_time>time) + return; + + if (navigation_goalrating_timeout(this)) + { + navigation_goalrating_start(this); + havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650); + havocbot_goalrating_ast_targets(this, 20000); + havocbot_goalrating_items(this, 15000, this.origin, 10000); + navigation_goalrating_end(this); + + navigation_goalrating_timeout_set(this); + } +} + +void havocbot_role_ast_defense(entity this) +{ + if(IS_DEAD(this)) + { + this.havocbot_attack_time = 0; + havocbot_ast_reset_role(this); + return; + } + + // Set the role timeout if necessary + if (!this.havocbot_role_timeout) + this.havocbot_role_timeout = time + 120; + + if (time > this.havocbot_role_timeout) + { + havocbot_ast_reset_role(this); + return; + } + + if(this.havocbot_attack_time>time) + return; + + if (navigation_goalrating_timeout(this)) + { + navigation_goalrating_start(this); + havocbot_goalrating_enemyplayers(this, 20000, this.origin, 3000); + havocbot_goalrating_ast_targets(this, 20000); + havocbot_goalrating_items(this, 15000, this.origin, 10000); + navigation_goalrating_end(this); + + navigation_goalrating_timeout_set(this); + } +} + +void havocbot_role_ast_setrole(entity this, float role) +{ + switch(role) + { + case HAVOCBOT_AST_ROLE_DEFENSE: + this.havocbot_role = havocbot_role_ast_defense; + this.havocbot_role_flags = HAVOCBOT_AST_ROLE_DEFENSE; + this.havocbot_role_timeout = 0; + break; + case HAVOCBOT_AST_ROLE_OFFENSE: + this.havocbot_role = havocbot_role_ast_offense; + this.havocbot_role_flags = HAVOCBOT_AST_ROLE_OFFENSE; + this.havocbot_role_timeout = 0; + break; + } +} + +void havocbot_ast_reset_role(entity this) +{ + if(IS_DEAD(this)) + return; + + if(this.team == assault_attacker_team) + havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_OFFENSE); + else + havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_DEFENSE); +} + +// mutator hooks +MUTATOR_HOOKFUNCTION(as, PlayerSpawn) +{ + entity player = M_ARGV(0, entity); + + if(player.team == assault_attacker_team) + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_ATTACKING); + else + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_DEFENDING); +} + +MUTATOR_HOOKFUNCTION(as, TurretSpawn) +{ + entity turret = M_ARGV(0, entity); + + if(!turret.team || turret.team == FLOAT_MAX) + turret.team = 5; // this gets reversed when match starts? +} + +MUTATOR_HOOKFUNCTION(as, VehicleInit) +{ + entity veh = M_ARGV(0, entity); + + veh.nextthink = time + 0.5; +} + +MUTATOR_HOOKFUNCTION(as, HavocBot_ChooseRole) +{ + entity bot = M_ARGV(0, entity); + + havocbot_ast_reset_role(bot); + return true; +} + +MUTATOR_HOOKFUNCTION(as, PlayHitsound) +{ + entity frag_victim = M_ARGV(0, entity); + + return (frag_victim.classname == "func_assault_destructible"); +} + +MUTATOR_HOOKFUNCTION(as, CheckAllowedTeams) +{ + // assault always has 2 teams + c1 = c2 = 0; + return true; +} + +MUTATOR_HOOKFUNCTION(as, CheckRules_World) +{ + M_ARGV(0, float) = WinningCondition_Assault(); + return true; +} + +MUTATOR_HOOKFUNCTION(as, ReadLevelCvars) +{ + // incompatible + warmup_stage = 0; + sv_ready_restart_after_countdown = 0; +} + +MUTATOR_HOOKFUNCTION(as, OnEntityPreSpawn) +{ + entity ent = M_ARGV(0, entity); + + switch(ent.classname) + { + case "info_player_team1": + case "info_player_team2": + case "info_player_team3": + case "info_player_team4": + return true; + } +} + +MUTATOR_HOOKFUNCTION(as, ReadyRestart_Deny) +{ + // readyrestart not supported (yet) + return true; +} +#endif diff --git a/qcsrc/common/gamemodes/gamemode/assault/assault.qh b/qcsrc/common/gamemodes/gamemode/assault/assault.qh new file mode 100644 index 000000000..d949f18b3 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/assault/assault.qh @@ -0,0 +1,48 @@ +#pragma once + +#ifdef SVQC +#include +#include + +const int ASSAULT_VALUE_INACTIVE = 1000; + +const int ST_ASSAULT_OBJECTIVES = 1; + +REGISTER_MUTATOR(as, false) +{ + MUTATOR_STATIC(); + MUTATOR_ONADD + { + GameRules_teams(true); + int teams = BITS(2); // always red vs blue + GameRules_scoring(teams, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, { + field_team(ST_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY); + field(SP_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY); + }); + } + return 0; +} + +// sprites +.entity assault_decreaser; +.entity assault_sprite; + +// legacy bot defs +const int HAVOCBOT_AST_ROLE_NONE = 0; +const int HAVOCBOT_AST_ROLE_DEFENSE = 2; +const int HAVOCBOT_AST_ROLE_OFFENSE = 4; + +.int havocbot_role_flags; +.float havocbot_attack_time; + +void(entity this) havocbot_role_ast_defense; +void(entity this) havocbot_role_ast_offense; + +void(entity bot) havocbot_ast_reset_role; + +void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrating_items; +void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers; + +// predefined spawnfuncs +void target_objective_decrease_activate(entity this); +#endif diff --git a/qcsrc/common/gamemodes/gamemode/clanarena/_mod.inc b/qcsrc/common/gamemodes/gamemode/clanarena/_mod.inc new file mode 100644 index 000000000..57dc9b3dd --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/clanarena/_mod.inc @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/clanarena/_mod.qh b/qcsrc/common/gamemodes/gamemode/clanarena/_mod.qh new file mode 100644 index 000000000..66f23740a --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/clanarena/_mod.qh @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/clanarena/clanarena.qc b/qcsrc/common/gamemodes/gamemode/clanarena/clanarena.qc new file mode 100644 index 000000000..561129c7d --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/clanarena/clanarena.qc @@ -0,0 +1,491 @@ +#include "clanarena.qh" + +// TODO: split into sv_clanarena +#ifdef SVQC +float autocvar_g_ca_damage2score_multiplier; +bool autocvar_g_ca_spectate_enemies; + +void CA_count_alive_players() +{ + total_players = redalive = bluealive = yellowalive = pinkalive = 0; + FOREACH_CLIENT(IS_PLAYER(it), { + switch(it.team) + { + case NUM_TEAM_1: ++total_players; if(!IS_DEAD(it)) ++redalive; break; + case NUM_TEAM_2: ++total_players; if(!IS_DEAD(it)) ++bluealive; break; + case NUM_TEAM_3: ++total_players; if(!IS_DEAD(it)) ++yellowalive; break; + case NUM_TEAM_4: ++total_players; if(!IS_DEAD(it)) ++pinkalive; break; + } + }); + FOREACH_CLIENT(IS_REAL_CLIENT(it), { + STAT(REDALIVE, it) = redalive; + STAT(BLUEALIVE, it) = bluealive; + STAT(YELLOWALIVE, it) = yellowalive; + STAT(PINKALIVE, it) = pinkalive; + }); +} + +float CA_GetWinnerTeam() +{ + float winner_team = 0; + if(redalive >= 1) + winner_team = NUM_TEAM_1; + if(bluealive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_2; + } + if(yellowalive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_3; + } + if(pinkalive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_4; + } + if(winner_team) + return winner_team; + return -1; // no player left +} + +void nades_Clear(entity player); + +#define CA_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0)) +#define CA_ALIVE_TEAMS_OK() (CA_ALIVE_TEAMS() == NumTeams(ca_teams)) +float CA_CheckWinner() +{ + if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER); + FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); }); + + allowed_to_spawn = false; + game_stopped = true; + round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit); + return 1; + } + + CA_count_alive_players(); + if(CA_ALIVE_TEAMS() > 1) + return 0; + + int winner_team = CA_GetWinnerTeam(); + if(winner_team > 0) + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN)); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN)); + TeamScore_AddToTeam(winner_team, ST_CA_ROUNDS, +1); + } + else if(winner_team == -1) + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED); + } + + allowed_to_spawn = false; + game_stopped = true; + round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit); + + FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); }); + + return 1; +} + +void CA_RoundStart() +{ + allowed_to_spawn = boolean(warmup_stage); +} + +bool CA_CheckTeams() +{ + static int prev_missing_teams_mask; + allowed_to_spawn = true; + CA_count_alive_players(); + if(CA_ALIVE_TEAMS_OK()) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + return true; + } + if(total_players == 0) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + return false; + } + int missing_teams_mask = 0; + if(ca_teams & BIT(0)) + missing_teams_mask += (!redalive) * 1; + if(ca_teams & BIT(1)) + missing_teams_mask += (!bluealive) * 2; + if(ca_teams & BIT(2)) + missing_teams_mask += (!yellowalive) * 4; + if(ca_teams & BIT(3)) + missing_teams_mask += (!pinkalive) * 8; + if(prev_missing_teams_mask != missing_teams_mask) + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask); + prev_missing_teams_mask = missing_teams_mask; + } + return false; +} + +bool ca_isEliminated(entity e) +{ + if(e.caplayer == 1 && (IS_DEAD(e) || e.frags == FRAGS_LMS_LOSER)) + return true; + if(e.caplayer == 0.5) + return true; + return false; +} + +/** Returns next available player to spectate if g_ca_spectate_enemies == 0 */ +entity CA_SpectateNext(entity player, entity start) +{ + if (SAME_TEAM(start, player)) return start; + // continue from current player + for (entity e = start; (e = find(e, classname, STR_PLAYER)); ) + { + if (SAME_TEAM(player, e)) return e; + } + // restart from begining + for (entity e = NULL; (e = find(e, classname, STR_PLAYER)); ) + { + if (SAME_TEAM(player, e)) return e; + } + return start; +} + + +MUTATOR_HOOKFUNCTION(ca, PlayerSpawn) +{ + entity player = M_ARGV(0, entity); + + player.caplayer = 1; + if (!warmup_stage) + eliminatedPlayers.SendFlags |= 1; +} + +MUTATOR_HOOKFUNCTION(ca, ForbidSpawn) +{ + entity player = M_ARGV(0, entity); + + // spectators / observers that weren't playing can join; they are + // immediately forced to observe in the PutClientInServer hook + // this way they are put in a team and can play in the next round + if (!allowed_to_spawn && player.caplayer) + return true; + return false; +} + +MUTATOR_HOOKFUNCTION(ca, PutClientInServer) +{ + entity player = M_ARGV(0, entity); + + if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join + { + TRANSMUTE(Observer, player); + if (CS(player).jointime != time && !player.caplayer) // not when connecting + { + player.caplayer = 0.5; + Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE); + } + } +} + +MUTATOR_HOOKFUNCTION(ca, reset_map_players) +{ + FOREACH_CLIENT(true, { + CS(it).killcount = 0; + if (!it.caplayer && IS_BOT_CLIENT(it)) + { + it.team = -1; + it.caplayer = 1; + } + if (it.caplayer) + { + TRANSMUTE(Player, it); + it.caplayer = 1; + PutClientInServer(it); + } + }); + bot_relinkplayerlist(); + return true; +} + +MUTATOR_HOOKFUNCTION(ca, ClientConnect) +{ + entity player = M_ARGV(0, entity); + + TRANSMUTE(Observer, player); + return true; +} + +MUTATOR_HOOKFUNCTION(ca, reset_map_global) +{ + allowed_to_spawn = true; + return true; +} + +MUTATOR_HOOKFUNCTION(ca, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE) +{ + M_ARGV(0, float) = ca_teams; +} + +entity ca_LastPlayerForTeam(entity this) +{ + entity last_pl = NULL; + FOREACH_CLIENT(IS_PLAYER(it) && it != this, { + if (!IS_DEAD(it)) + if (SAME_TEAM(this, it)) + if (!last_pl) + last_pl = it; + else + return NULL; + }); + return last_pl; +} + +void ca_LastPlayerForTeam_Notify(entity this) +{ + if (round_handler_IsActive()) + if (round_handler_IsRoundStarted()) + { + entity pl = ca_LastPlayerForTeam(this); + if (pl) + Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE); + } +} + +MUTATOR_HOOKFUNCTION(ca, PlayerDies) +{ + entity frag_target = M_ARGV(2, entity); + + ca_LastPlayerForTeam_Notify(frag_target); + if (!allowed_to_spawn) + { + frag_target.respawn_flags = RESPAWN_SILENT; + // prevent unwanted sudden rejoin as spectator and movement of spectator camera + frag_target.respawn_time = time + 2; + } + frag_target.respawn_flags |= RESPAWN_FORCE; + if (!warmup_stage) + eliminatedPlayers.SendFlags |= 1; + if(IS_BOT_CLIENT(frag_target)) + bot_clear(frag_target); + return true; +} + +MUTATOR_HOOKFUNCTION(ca, ClientDisconnect) +{ + entity player = M_ARGV(0, entity); + + if (player.caplayer == 1) + ca_LastPlayerForTeam_Notify(player); + return true; +} + +MUTATOR_HOOKFUNCTION(ca, MakePlayerObserver) +{ + entity player = M_ARGV(0, entity); + + if (!IS_DEAD(player)) + ca_LastPlayerForTeam_Notify(player); + if (player.killindicator_teamchange == -2) // player wants to spectate + player.caplayer = 0; + if (player.caplayer) + player.frags = FRAGS_LMS_LOSER; + if (!warmup_stage) + eliminatedPlayers.SendFlags |= 1; + if (!player.caplayer) + return false; // allow team reset + return true; // prevent team reset +} + +MUTATOR_HOOKFUNCTION(ca, ForbidThrowCurrentWeapon) +{ + return true; +} + +MUTATOR_HOOKFUNCTION(ca, GiveFragsForKill, CBC_ORDER_FIRST) +{ + M_ARGV(2, float) = 0; // score will be given to the winner team when the round ends + return true; +} + +MUTATOR_HOOKFUNCTION(ca, SetStartItems) +{ + start_items &= ~IT_UNLIMITED_AMMO; + start_health = warmup_start_health = cvar("g_lms_start_health"); + start_armorvalue = warmup_start_armorvalue = cvar("g_lms_start_armor"); + start_ammo_shells = warmup_start_ammo_shells = cvar("g_lms_start_ammo_shells"); + start_ammo_nails = warmup_start_ammo_nails = cvar("g_lms_start_ammo_nails"); + start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets"); + start_ammo_cells = warmup_start_ammo_cells = cvar("g_lms_start_ammo_cells"); + start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_lms_start_ammo_plasma"); + start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_lms_start_ammo_fuel"); +} + +MUTATOR_HOOKFUNCTION(ca, Damage_Calculate) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + float frag_deathtype = M_ARGV(3, float); + float frag_damage = M_ARGV(4, float); + float frag_mirrordamage = M_ARGV(5, float); + + if (IS_PLAYER(frag_target)) + if (!IS_DEAD(frag_target)) + if (frag_target == frag_attacker || SAME_TEAM(frag_target, frag_attacker) || frag_deathtype == DEATH_FALL.m_id) + frag_damage = 0; + + frag_mirrordamage = 0; + + M_ARGV(4, float) = frag_damage; + M_ARGV(5, float) = frag_mirrordamage; +} + +MUTATOR_HOOKFUNCTION(ca, FilterItem) +{ + entity item = M_ARGV(0, entity); + + if (autocvar_g_powerups <= 0) + if (item.flags & FL_POWERUP) + return true; + + if (autocvar_g_pickup_items <= 0) + return true; +} + +MUTATOR_HOOKFUNCTION(ca, PlayerDamage_SplitHealthArmor) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + float frag_damage = M_ARGV(7, float); + float damage_take = M_ARGV(4, float); + float damage_save = M_ARGV(5, float); + + float excess = max(0, frag_damage - damage_take - damage_save); + + if (frag_target != frag_attacker && IS_PLAYER(frag_attacker)) + GameRules_scoring_add_team(frag_attacker, SCORE, (frag_damage - excess) * autocvar_g_ca_damage2score_multiplier); +} + +MUTATOR_HOOKFUNCTION(ca, CalculateRespawnTime) +{ + // no respawn calculations needed, player is forced to spectate anyway + return true; +} + +MUTATOR_HOOKFUNCTION(ca, PlayerRegen) +{ + // no regeneration in CA + return true; +} + +MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining) +{ + // announce remaining frags + return true; +} + +MUTATOR_HOOKFUNCTION(ca, SpectateSet) +{ + entity client = M_ARGV(0, entity); + entity targ = M_ARGV(1, entity); + + if (!autocvar_g_ca_spectate_enemies && client.caplayer) + if (DIFF_TEAM(targ, client)) + return true; +} + +MUTATOR_HOOKFUNCTION(ca, SpectateNext) +{ + entity client = M_ARGV(0, entity); + + if (!autocvar_g_ca_spectate_enemies && client.caplayer) + { + entity targ = M_ARGV(1, entity); + M_ARGV(1, entity) = CA_SpectateNext(client, targ); + return true; + } +} + +MUTATOR_HOOKFUNCTION(ca, SpectatePrev) +{ + entity client = M_ARGV(0, entity); + entity targ = M_ARGV(1, entity); + entity first = M_ARGV(2, entity); + + if (!autocvar_g_ca_spectate_enemies && client.caplayer) + { + do { targ = targ.chain; } + while(targ && DIFF_TEAM(targ, client)); + + if (!targ) + { + for (targ = first; targ && DIFF_TEAM(targ, client); targ = targ.chain); + + if (targ == client.enemy) + return MUT_SPECPREV_RETURN; + } + } + + M_ARGV(1, entity) = targ; + + return MUT_SPECPREV_FOUND; +} + +MUTATOR_HOOKFUNCTION(ca, Bot_FixCount, CBC_ORDER_EXCLUSIVE) +{ + FOREACH_CLIENT(IS_REAL_CLIENT(it), { + if (IS_PLAYER(it) || it.caplayer == 1) + ++M_ARGV(0, int); + ++M_ARGV(1, int); + }); + return true; +} + +MUTATOR_HOOKFUNCTION(ca, ClientCommand_Spectate) +{ + entity player = M_ARGV(0, entity); + + if (player.caplayer) + { + // they're going to spec, we can do other checks + if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player))) + Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE); + return MUT_SPECCMD_FORCE; + } + + return MUT_SPECCMD_CONTINUE; +} + +MUTATOR_HOOKFUNCTION(ca, WantWeapon) +{ + M_ARGV(2, bool) = true; // all weapons +} + +MUTATOR_HOOKFUNCTION(ca, HideTeamNagger) +{ + return true; // doesn't work well with the whole spectator as player thing +} + +MUTATOR_HOOKFUNCTION(ca, GetPlayerStatus) +{ + entity player = M_ARGV(0, entity); + + return player.caplayer == 1; +} + +MUTATOR_HOOKFUNCTION(ca, SetWeaponArena) +{ + // most weapons arena + if (M_ARGV(0, string) == "0" || M_ARGV(0, string) == "") M_ARGV(0, string) = "most"; +} +#endif diff --git a/qcsrc/common/gamemodes/gamemode/clanarena/clanarena.qh b/qcsrc/common/gamemodes/gamemode/clanarena/clanarena.qh new file mode 100644 index 000000000..8a94acd18 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/clanarena/clanarena.qh @@ -0,0 +1,56 @@ +#pragma once + +#ifdef SVQC +#include +#include +#include + +int autocvar_g_ca_point_limit; +int autocvar_g_ca_point_leadlimit; +float autocvar_g_ca_round_timelimit; +bool autocvar_g_ca_team_spawns; +//int autocvar_g_ca_teams; +int autocvar_g_ca_teams_override; +float autocvar_g_ca_warmup; + + +int ca_teams; +bool allowed_to_spawn; + +const int ST_CA_ROUNDS = 1; + +bool CA_CheckTeams(); +bool CA_CheckWinner(); +void CA_RoundStart(); +bool ca_isEliminated(entity e); + +REGISTER_MUTATOR(ca, false) +{ + MUTATOR_STATIC(); + MUTATOR_ONADD + { + GameRules_teams(true); + GameRules_spawning_teams(autocvar_g_ca_team_spawns); + GameRules_limit_score(autocvar_g_ca_point_limit); + GameRules_limit_lead(autocvar_g_ca_point_leadlimit); + + ca_teams = autocvar_g_ca_teams_override; + if (ca_teams < 2) + ca_teams = cvar("g_ca_teams"); // read the cvar directly as it gets written earlier in the same frame + + ca_teams = BITS(bound(2, ca_teams, 4)); + GameRules_scoring(ca_teams, SFL_SORT_PRIO_PRIMARY, 0, { + field_team(ST_CA_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY); + }); + + allowed_to_spawn = true; + round_handler_Spawn(CA_CheckTeams, CA_CheckWinner, CA_RoundStart); + round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit); + EliminatedPlayers_Init(ca_isEliminated); + } + return 0; +} + +// should be removed in the future, as other code should not have to care +.float caplayer; // 0.5 if scheduled to join the next round +#endif diff --git a/qcsrc/common/gamemodes/gamemode/ctf/_mod.inc b/qcsrc/common/gamemodes/gamemode/ctf/_mod.inc new file mode 100644 index 000000000..dcd813569 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/ctf/_mod.inc @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/ctf/_mod.qh b/qcsrc/common/gamemodes/gamemode/ctf/_mod.qh new file mode 100644 index 000000000..c1ddd9731 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/ctf/_mod.qh @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/ctf/ctf.qc b/qcsrc/common/gamemodes/gamemode/ctf/ctf.qc new file mode 100644 index 000000000..9382c9d8d --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/ctf/ctf.qc @@ -0,0 +1,2779 @@ +#include "ctf.qh" + +// TODO: split into sv_ctf +#ifdef SVQC +#include +#include +#include + +#include + +bool autocvar_g_ctf_allow_vehicle_carry; +bool autocvar_g_ctf_allow_vehicle_touch; +bool autocvar_g_ctf_allow_monster_touch; +bool autocvar_g_ctf_throw; +float autocvar_g_ctf_throw_angle_max; +float autocvar_g_ctf_throw_angle_min; +int autocvar_g_ctf_throw_punish_count; +float autocvar_g_ctf_throw_punish_delay; +float autocvar_g_ctf_throw_punish_time; +float autocvar_g_ctf_throw_strengthmultiplier; +float autocvar_g_ctf_throw_velocity_forward; +float autocvar_g_ctf_throw_velocity_up; +float autocvar_g_ctf_drop_velocity_up; +float autocvar_g_ctf_drop_velocity_side; +bool autocvar_g_ctf_oneflag_reverse; +bool autocvar_g_ctf_portalteleport; +bool autocvar_g_ctf_pass; +float autocvar_g_ctf_pass_arc; +float autocvar_g_ctf_pass_arc_max; +float autocvar_g_ctf_pass_directional_max; +float autocvar_g_ctf_pass_directional_min; +float autocvar_g_ctf_pass_radius; +float autocvar_g_ctf_pass_wait; +bool autocvar_g_ctf_pass_request; +float autocvar_g_ctf_pass_turnrate; +float autocvar_g_ctf_pass_timelimit; +float autocvar_g_ctf_pass_velocity; +bool autocvar_g_ctf_dynamiclights; +float autocvar_g_ctf_flag_collect_delay; +float autocvar_g_ctf_flag_damageforcescale; +bool autocvar_g_ctf_flag_dropped_waypoint; +bool autocvar_g_ctf_flag_dropped_floatinwater; +bool autocvar_g_ctf_flag_glowtrails; +int autocvar_g_ctf_flag_health; +bool autocvar_g_ctf_flag_return; +bool autocvar_g_ctf_flag_return_carrying; +float autocvar_g_ctf_flag_return_carried_radius; +float autocvar_g_ctf_flag_return_time; +bool autocvar_g_ctf_flag_return_when_unreachable; +float autocvar_g_ctf_flag_return_damage; +float autocvar_g_ctf_flag_return_damage_delay; +float autocvar_g_ctf_flag_return_dropped; +float autocvar_g_ctf_flagcarrier_auto_helpme_damage; +float autocvar_g_ctf_flagcarrier_auto_helpme_time; +float autocvar_g_ctf_flagcarrier_selfdamagefactor; +float autocvar_g_ctf_flagcarrier_selfforcefactor; +float autocvar_g_ctf_flagcarrier_damagefactor; +float autocvar_g_ctf_flagcarrier_forcefactor; +//float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting; +bool autocvar_g_ctf_fullbrightflags; +bool autocvar_g_ctf_ignore_frags; +bool autocvar_g_ctf_score_ignore_fields; +int autocvar_g_ctf_score_capture; +int autocvar_g_ctf_score_capture_assist; +int autocvar_g_ctf_score_kill; +int autocvar_g_ctf_score_penalty_drop; +int autocvar_g_ctf_score_penalty_returned; +int autocvar_g_ctf_score_pickup_base; +int autocvar_g_ctf_score_pickup_dropped_early; +int autocvar_g_ctf_score_pickup_dropped_late; +int autocvar_g_ctf_score_return; +float autocvar_g_ctf_shield_force; +float autocvar_g_ctf_shield_max_ratio; +int autocvar_g_ctf_shield_min_negscore; +bool autocvar_g_ctf_stalemate; +int autocvar_g_ctf_stalemate_endcondition; +float autocvar_g_ctf_stalemate_time; +bool autocvar_g_ctf_reverse; +float autocvar_g_ctf_dropped_capture_delay; +float autocvar_g_ctf_dropped_capture_radius; + +void ctf_FakeTimeLimit(entity e, float t) +{ + msg_entity = e; + WriteByte(MSG_ONE, 3); // svc_updatestat + WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT + if(t < 0) + WriteCoord(MSG_ONE, autocvar_timelimit); + else + WriteCoord(MSG_ONE, (t + 1) / 60); +} + +void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later +{ + if(autocvar_sv_eventlog) + GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != NULL) ? ftos(actor.playerid) : ""))); + //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : ""))); +} + +void ctf_CaptureRecord(entity flag, entity player) +{ + float cap_record = ctf_captimerecord; + float cap_time = (time - flag.ctf_pickuptime); + string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname")); + + // notify about shit + if(ctf_oneflag) + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); + else if(!ctf_captimerecord) + Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, TIME_ENCODE(cap_time)); + else if(cap_time < cap_record) + Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_BROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record)); + else + Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_UNBROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record)); + + // write that shit in the database + if(!ctf_oneflag) // but not in 1-flag mode + if((!ctf_captimerecord) || (cap_time < cap_record)) + { + ctf_captimerecord = cap_time; + db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time)); + db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname); + write_recordmarker(player, flag.ctf_pickuptime, cap_time); + } + + if(autocvar_g_ctf_leaderboard && !ctf_oneflag) + race_setTime(GetMapname(), TIME_ENCODE(cap_time), player.crypto_idfp, player.netname, player, false); +} + +bool ctf_Immediate_Return_Allowed(entity flag, entity toucher) +{ + int num_perteam = 0; + FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; }); + + // automatically return if there's only 1 player on the team + return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried)) + && flag.team); +} + +bool ctf_Return_Customize(entity this, entity client) +{ + // only to the carrier + return boolean(client == this.owner); +} + +void ctf_FlagcarrierWaypoints(entity player) +{ + WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG); + WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2); + WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id)); + WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team)); + + if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried)) + { + if(!player.wps_enemyflagcarrier) + { + entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG); + wp.colormod = WPCOLOR_ENEMYFC(player.team); + setcefc(wp, ctf_Stalemate_Customize); + + if(IS_REAL_CLIENT(player) && !ctf_stalemate) + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE); + } + + if(!player.wps_flagreturn) + { + entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG); + owp.colormod = '0 0.8 0.8'; + //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1')); + setcefc(owp, ctf_Return_Customize); + } + } +} + +void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate) +{ + float current_distance = vlen((('1 0 0' * to.x) + ('0 1 0' * to.y)) - (('1 0 0' * from.x) + ('0 1 0' * from.y))); // for the sake of this check, exclude Z axis + float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc))); + float current_height = (initial_height * min(1, (current_distance / flag.pass_distance))); + //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n"); + + vector targpos; + if(current_height) // make sure we can actually do this arcing path + { + targpos = (to + ('0 0 1' * current_height)); + WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag); + if(trace_fraction < 1) + { + //print("normal arc line failed, trying to find new pos..."); + WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag); + targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET); + WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag); + if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ } + /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */ + } + } + else { targpos = to; } + + //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y)); + + vector desired_direction = normalize(targpos - from); + if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); } + else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); } +} + +bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer) +{ + if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min) + { + // directional tracing only + float spreadlimit; + makevectors(passer_angle); + + // find the closest point on the enemy to the center of the attack + float h; // hypotenuse, which is the distance between attacker to head + float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin + + h = vlen(head_center - passer_center); + a = h * (normalize(head_center - passer_center) * v_forward); + + vector nearest_on_line = (passer_center + a * v_forward); + float distance_from_line = vlen(nearest_to_passer - nearest_on_line); + + spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1); + spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit); + + if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90)) + { return true; } + else + { return false; } + } + else { return true; } +} + + +// ======================= +// CaptureShield Functions +// ======================= + +bool ctf_CaptureShield_CheckStatus(entity p) +{ + int s, s2, s3, s4, se, se2, se3, se4, sr, ser; + int players_worseeq, players_total; + + if(ctf_captureshield_max_ratio <= 0) + return false; + + s = GameRules_scoring_add(p, CTF_CAPS, 0); + s2 = GameRules_scoring_add(p, CTF_PICKUPS, 0); + s3 = GameRules_scoring_add(p, CTF_RETURNS, 0); + s4 = GameRules_scoring_add(p, CTF_FCKILLS, 0); + + sr = ((s - s2) + (s3 + s4)); + + if(sr >= -ctf_captureshield_min_negscore) + return false; + + players_total = players_worseeq = 0; + FOREACH_CLIENT(IS_PLAYER(it), { + if(DIFF_TEAM(it, p)) + continue; + se = GameRules_scoring_add(it, CTF_CAPS, 0); + se2 = GameRules_scoring_add(it, CTF_PICKUPS, 0); + se3 = GameRules_scoring_add(it, CTF_RETURNS, 0); + se4 = GameRules_scoring_add(it, CTF_FCKILLS, 0); + + ser = ((se - se2) + (se3 + se4)); + + if(ser <= sr) + ++players_worseeq; + ++players_total; + }); + + // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse + // use this rule here + + if(players_worseeq >= players_total * ctf_captureshield_max_ratio) + return false; + + return true; +} + +void ctf_CaptureShield_Update(entity player, bool wanted_status) +{ + bool updated_status = ctf_CaptureShield_CheckStatus(player); + if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only + { + Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE)); + player.ctf_captureshielded = updated_status; + } +} + +bool ctf_CaptureShield_Customize(entity this, entity client) +{ + if(!client.ctf_captureshielded) { return false; } + if(CTF_SAMETEAM(this, client)) { return false; } + + return true; +} + +void ctf_CaptureShield_Touch(entity this, entity toucher) +{ + if(!toucher.ctf_captureshielded) { return; } + if(CTF_SAMETEAM(this, toucher)) { return; } + + vector mymid = (this.absmin + this.absmax) * 0.5; + vector theirmid = (toucher.absmin + toucher.absmax) * 0.5; + + Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, mymid, normalize(theirmid - mymid) * ctf_captureshield_force); + if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); } +} + +void ctf_CaptureShield_Spawn(entity flag) +{ + entity shield = new(ctf_captureshield); + + shield.enemy = flag; + shield.team = flag.team; + settouch(shield, ctf_CaptureShield_Touch); + setcefc(shield, ctf_CaptureShield_Customize); + shield.effects = EF_ADDITIVE; + set_movetype(shield, MOVETYPE_NOCLIP); + shield.solid = SOLID_TRIGGER; + shield.avelocity = '7 0 11'; + shield.scale = 0.5; + + setorigin(shield, flag.origin); + setmodel(shield, MDL_CTF_SHIELD); + setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs); +} + + +// ==================== +// Drop/Pass/Throw Code +// ==================== + +void ctf_Handle_Drop(entity flag, entity player, int droptype) +{ + // declarations + player = (player ? player : flag.pass_sender); + + // main + set_movetype(flag, MOVETYPE_TOSS); + flag.takedamage = DAMAGE_YES; + flag.angles = '0 0 0'; + flag.health = flag.max_flag_health; + flag.ctf_droptime = time; + flag.ctf_dropper = player; + flag.ctf_status = FLAG_DROPPED; + + // messages and sounds + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname); + _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE); + ctf_EventLog("dropped", player.team, player); + + // scoring + GameRules_scoring_add_team(player, SCORE, -((flag.score_drop) ? flag.score_drop : autocvar_g_ctf_score_penalty_drop)); + GameRules_scoring_add(player, CTF_DROPS, 1); + + // waypoints + if(autocvar_g_ctf_flag_dropped_waypoint) { + entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, NULL, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, true, RADARICON_FLAG); + wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team); + } + + if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health)) + { + WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health); + WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); + } + + player.throw_antispam = time + autocvar_g_ctf_pass_wait; + + if(droptype == DROP_PASS) + { + flag.pass_distance = 0; + flag.pass_sender = NULL; + flag.pass_target = NULL; + } +} + +void ctf_Handle_Retrieve(entity flag, entity player) +{ + entity sender = flag.pass_sender; + + // transfer flag to player + flag.owner = player; + flag.owner.flagcarried = flag; + GameRules_scoring_vip(player, true); + + // reset flag + if(player.vehicle) + { + setattachment(flag, player.vehicle, ""); + setorigin(flag, VEHICLE_FLAG_OFFSET); + flag.scale = VEHICLE_FLAG_SCALE; + } + else + { + setattachment(flag, player, ""); + setorigin(flag, FLAG_CARRY_OFFSET); + } + set_movetype(flag, MOVETYPE_NONE); + flag.takedamage = DAMAGE_NO; + flag.solid = SOLID_NOT; + flag.angles = '0 0 0'; + flag.ctf_status = FLAG_CARRY; + + // messages and sounds + _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM); + ctf_EventLog("receive", flag.team, player); + + FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { + if(it == sender) + Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname); + else if(it == player) + Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname); + else if(SAME_TEAM(it, sender)) + Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), sender.netname, player.netname); + }); + + // create new waypoint + ctf_FlagcarrierWaypoints(player); + + sender.throw_antispam = time + autocvar_g_ctf_pass_wait; + player.throw_antispam = sender.throw_antispam; + + flag.pass_distance = 0; + flag.pass_sender = NULL; + flag.pass_target = NULL; +} + +void ctf_Handle_Throw(entity player, entity receiver, int droptype) +{ + entity flag = player.flagcarried; + vector targ_origin, flag_velocity; + + if(!flag) { return; } + if((droptype == DROP_PASS) && !receiver) { return; } + + if(flag.speedrunning) { ctf_RespawnFlag(flag); return; } + + // reset the flag + setattachment(flag, NULL, ""); + setorigin(flag, player.origin + FLAG_DROP_OFFSET); + flag.owner.flagcarried = NULL; + GameRules_scoring_vip(flag.owner, false); + flag.owner = NULL; + flag.solid = SOLID_TRIGGER; + flag.ctf_dropper = player; + flag.ctf_droptime = time; + navigation_dynamicgoal_set(flag); + + flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS + + switch(droptype) + { + case DROP_PASS: + { + // warpzone support: + // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver + // findradius has already put wzn ... wz1 into receiver's warpzone parameters! + WarpZone_RefSys_Copy(flag, receiver); + WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver + targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag + + flag.pass_distance = vlen((('1 0 0' * targ_origin.x) + ('0 1 0' * targ_origin.y)) - (('1 0 0' * player.origin.x) + ('0 1 0' * player.origin.y))); // for the sake of this check, exclude Z axis + ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false); + + // main + set_movetype(flag, MOVETYPE_FLY); + flag.takedamage = DAMAGE_NO; + flag.pass_sender = player; + flag.pass_target = receiver; + flag.ctf_status = FLAG_PASSING; + + // other + _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM); + WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin); + ctf_EventLog("pass", flag.team, player); + break; + } + + case DROP_THROW: + { + makevectors((player.v_angle.y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle.x, autocvar_g_ctf_throw_angle_max) * '1 0 0')); + + flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & ITEM_Strength.m_itemid) ? autocvar_g_ctf_throw_strengthmultiplier : 1))); + flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false); + ctf_Handle_Drop(flag, player, droptype); + break; + } + + case DROP_RESET: + { + flag.velocity = '0 0 0'; // do nothing + break; + } + + default: + case DROP_NORMAL: + { + flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), false); + ctf_Handle_Drop(flag, player, droptype); + break; + } + } + + // kill old waypointsprite + WaypointSprite_Ping(player.wps_flagcarrier); + WaypointSprite_Kill(player.wps_flagcarrier); + + if(player.wps_enemyflagcarrier) + WaypointSprite_Kill(player.wps_enemyflagcarrier); + + if(player.wps_flagreturn) + WaypointSprite_Kill(player.wps_flagreturn); + + // captureshield + ctf_CaptureShield_Update(player, 0); // shield player from picking up flag +} + +void shockwave_spawn(string m, vector org, float sz, float t1, float t2) +{ + return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2); +} + +// ============== +// Event Handlers +// ============== + +void nades_GiveBonus(entity player, float score); + +void ctf_Handle_Capture(entity flag, entity toucher, int capturetype) +{ + entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher); + entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper); + entity player_team_flag = NULL, tmp_entity; + float old_time, new_time; + + if(!player) { return; } // without someone to give the reward to, we can't possibly cap + if(CTF_DIFFTEAM(player, flag)) { return; } + if((flag.cnt || enemy_flag.cnt) && flag.cnt != enemy_flag.cnt) { return; } // this should catch some edge cases (capturing grouped flag at ungrouped flag disallowed etc) + + if (toucher.goalentity == flag.bot_basewaypoint) + toucher.goalentity_lock_timeout = 0; + + if(ctf_oneflag) + for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) + if(SAME_TEAM(tmp_entity, player)) + { + player_team_flag = tmp_entity; + break; + } + + nades_GiveBonus(player, autocvar_g_nades_bonus_score_high ); + + player.throw_prevtime = time; + player.throw_count = 0; + + // messages and sounds + Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE)); + ctf_CaptureRecord(enemy_flag, player); + _sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE); + + switch(capturetype) + { + case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break; + case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break; + default: break; + } + + // scoring + float pscore = 0; + if(enemy_flag.score_capture || flag.score_capture) + pscore = floor((max(1, enemy_flag.score_capture) + max(1, flag.score_capture)) * 0.5); + GameRules_scoring_add_team(player, SCORE, ((pscore) ? pscore : autocvar_g_ctf_score_capture)); + float capscore = 0; + if(enemy_flag.score_team_capture || flag.score_team_capture) + capscore = floor((max(1, enemy_flag.score_team_capture) + max(1, flag.score_team_capture)) * 0.5); + GameRules_scoring_add_team(player, CTF_CAPS, ((capscore) ? capscore : 1)); + + old_time = GameRules_scoring_add(player, CTF_CAPTIME, 0); + new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime); + if(!old_time || new_time < old_time) + GameRules_scoring_add(player, CTF_CAPTIME, new_time - old_time); + + // effects + Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1); + //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1); + + // other + if(capturetype == CAPTURE_NORMAL) + { + WaypointSprite_Kill(player.wps_flagcarrier); + if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); } + + if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper)) + { GameRules_scoring_add_team(enemy_flag.ctf_dropper, SCORE, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); } + } + + flag.enemy = toucher; + + // reset the flag + player.next_take_time = time + autocvar_g_ctf_flag_collect_delay; + ctf_RespawnFlag(enemy_flag); +} + +void ctf_Handle_Return(entity flag, entity player) +{ + // messages and sounds + if(IS_MONSTER(player)) + { + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name); + } + else if(flag.team) + { + Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN)); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN), player.netname); + } + _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE); + ctf_EventLog("return", flag.team, player); + + // scoring + if(IS_PLAYER(player)) + { + GameRules_scoring_add_team(player, SCORE, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return + GameRules_scoring_add(player, CTF_RETURNS, 1); // add to count of returns + + nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium); + } + + TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it + + if(flag.ctf_dropper) + { + GameRules_scoring_add(flag.ctf_dropper, SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag + ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag + flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time + } + + // other + if(player.flagcarried == flag) + WaypointSprite_Kill(player.wps_flagcarrier); + + flag.enemy = player; + + // reset the flag + ctf_RespawnFlag(flag); +} + +void ctf_Handle_Pickup(entity flag, entity player, int pickuptype) +{ + // declarations + float pickup_dropped_score; // used to calculate dropped pickup score + + // attach the flag to the player + flag.owner = player; + player.flagcarried = flag; + GameRules_scoring_vip(player, true); + if(player.vehicle) + { + setattachment(flag, player.vehicle, ""); + setorigin(flag, VEHICLE_FLAG_OFFSET); + flag.scale = VEHICLE_FLAG_SCALE; + } + else + { + setattachment(flag, player, ""); + setorigin(flag, FLAG_CARRY_OFFSET); + } + + // flag setup + set_movetype(flag, MOVETYPE_NONE); + flag.takedamage = DAMAGE_NO; + flag.solid = SOLID_NOT; + flag.angles = '0 0 0'; + flag.ctf_status = FLAG_CARRY; + + switch(pickuptype) + { + case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs + case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit + default: break; + } + + // messages and sounds + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), player.netname); + if(ctf_stalemate) + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); + if(!flag.team) + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); + else if(CTF_DIFFTEAM(player, flag)) + Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_PICKUP)); + else + Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team)); + + Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname); + + if(!flag.team) + FOREACH_CLIENT(IS_PLAYER(it) && it != player && DIFF_TEAM(it, player), { Send_Notification(NOTIF_ONE, it, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname); }); + + if(flag.team) + FOREACH_CLIENT(IS_PLAYER(it) && it != player, { + if(CTF_SAMETEAM(flag, it)) + if(SAME_TEAM(player, it)) + Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname); + else + Send_Notification(NOTIF_ONE, it, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname); + }); + + _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE); + + // scoring + GameRules_scoring_add(player, CTF_PICKUPS, 1); + nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor); + switch(pickuptype) + { + case PICKUP_BASE: + { + GameRules_scoring_add_team(player, SCORE, ((flag.score_pickup) ? flag.score_pickup : autocvar_g_ctf_score_pickup_base)); + ctf_EventLog("steal", flag.team, player); + break; + } + + case PICKUP_DROPPED: + { + pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1); + pickup_dropped_score = floor((autocvar_g_ctf_score_pickup_dropped_late * (1 - pickup_dropped_score) + autocvar_g_ctf_score_pickup_dropped_early * pickup_dropped_score) + 0.5); + LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score)); + GameRules_scoring_add_team(player, SCORE, pickup_dropped_score); + ctf_EventLog("pickup", flag.team, player); + break; + } + + default: break; + } + + // speedrunning + if(pickuptype == PICKUP_BASE) + { + flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record + if((player.speedrunning) && (ctf_captimerecord)) + ctf_FakeTimeLimit(player, time + ctf_captimerecord); + } + + // effects + Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1); + + // waypoints + if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); } + ctf_FlagcarrierWaypoints(player); + WaypointSprite_Ping(player.wps_flagcarrier); +} + + +// =================== +// Main Flag Functions +// =================== + +void ctf_CheckFlagReturn(entity flag, int returntype) +{ + if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING)) + { + if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); } + + if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time)) + { + switch(returntype) + { + case RETURN_DROPPED: + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break; + case RETURN_DAMAGE: + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break; + case RETURN_SPEEDRUN: + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), TIME_ENCODE(ctf_captimerecord)); break; + case RETURN_NEEDKILL: + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break; + default: + case RETURN_TIMEOUT: + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break; + } + _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE); + ctf_EventLog("returned", flag.team, NULL); + flag.enemy = NULL; + ctf_RespawnFlag(flag); + } + } +} + +bool ctf_Stalemate_Customize(entity this, entity client) +{ + // make spectators see what the player would see + entity e = WaypointSprite_getviewentity(client); + entity wp_owner = this.owner; + + // team waypoints + //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; } + if(SAME_TEAM(wp_owner, e)) { return false; } + if(!IS_PLAYER(e)) { return false; } + + return true; +} + +void ctf_CheckStalemate() +{ + // declarations + int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0; + entity tmp_entity; + + entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs + + // build list of stale flags + for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) + { + if(autocvar_g_ctf_stalemate) + if(tmp_entity.ctf_status != FLAG_BASE) + if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag + { + tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist + ctf_staleflaglist = tmp_entity; + + switch(tmp_entity.team) + { + case NUM_TEAM_1: ++stale_red_flags; break; + case NUM_TEAM_2: ++stale_blue_flags; break; + case NUM_TEAM_3: ++stale_yellow_flags; break; + case NUM_TEAM_4: ++stale_pink_flags; break; + default: ++stale_neutral_flags; break; + } + } + } + + if(ctf_oneflag) + stale_flags = (stale_neutral_flags >= 1); + else + stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1); + + if(ctf_oneflag && stale_flags == 1) + ctf_stalemate = true; + else if(stale_flags >= 2) + ctf_stalemate = true; + else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2) + { ctf_stalemate = false; wpforenemy_announced = false; } + else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1) + { ctf_stalemate = false; wpforenemy_announced = false; } + + // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary + if(ctf_stalemate) + { + for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext) + { + if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier)) + { + entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, NULL, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG); + wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team); + setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize); + } + } + + if (!wpforenemy_announced) + { + FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((it.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER)); }); + + wpforenemy_announced = true; + } + } +} + +void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) +{ + if(ITEM_DAMAGE_NEEDKILL(deathtype)) + { + if(autocvar_g_ctf_flag_return_damage_delay) + this.ctf_flagdamaged_byworld = true; + else + { + this.health = 0; + ctf_CheckFlagReturn(this, RETURN_NEEDKILL); + } + return; + } + if(autocvar_g_ctf_flag_return_damage) + { + // reduce health and check if it should be returned + this.health = this.health - damage; + ctf_CheckFlagReturn(this, RETURN_DAMAGE); + return; + } +} + +void ctf_FlagThink(entity this) +{ + // declarations + entity tmp_entity; + + this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary. + + // captureshield + if(this == ctf_worldflaglist) // only for the first flag + FOREACH_CLIENT(true, { ctf_CaptureShield_Update(it, 1); }); // release shield only + + // sanity checks + if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished + LOG_TRACE("wtf the flag got squashed?"); + tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this); + if(!trace_startsolid || this.noalign) // can we resize it without getting stuck? + setsize(this, this.m_mins, this.m_maxs); + } + + // main think method + switch(this.ctf_status) + { + case FLAG_BASE: + { + if(autocvar_g_ctf_dropped_capture_radius) + { + for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) + if(tmp_entity.ctf_status == FLAG_DROPPED) + if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius)) + if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay) + ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED); + } + return; + } + + case FLAG_DROPPED: + { + this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it + + if(autocvar_g_ctf_flag_dropped_floatinwater) + { + vector midpoint = ((this.absmin + this.absmax) * 0.5); + if(pointcontents(midpoint) == CONTENT_WATER) + { + this.velocity = this.velocity * 0.5; + + if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER) + { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; } + else + { set_movetype(this, MOVETYPE_FLY); } + } + else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); } + } + if(autocvar_g_ctf_flag_return_dropped) + { + if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1)) + { + this.health = 0; + ctf_CheckFlagReturn(this, RETURN_DROPPED); + return; + } + } + if(this.ctf_flagdamaged_byworld) + { + this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE); + ctf_CheckFlagReturn(this, RETURN_NEEDKILL); + return; + } + else if(autocvar_g_ctf_flag_return_time) + { + this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE); + ctf_CheckFlagReturn(this, RETURN_TIMEOUT); + return; + } + return; + } + + case FLAG_CARRY: + { + if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord)) + { + this.health = 0; + ctf_CheckFlagReturn(this, RETURN_SPEEDRUN); + + CS(this.owner).impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set + ImpulseCommands(this.owner); + } + if(autocvar_g_ctf_stalemate) + { + if(time >= wpforenemy_nextthink) + { + ctf_CheckStalemate(); + wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check) + } + } + if(CTF_SAMETEAM(this, this.owner) && this.team) + { + if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed + ctf_Handle_Throw(this.owner, NULL, DROP_THROW); + else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius)) + ctf_Handle_Return(this, this.owner); + } + return; + } + + case FLAG_PASSING: + { + vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5); + targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us) + WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this); + + if((this.pass_target == NULL) + || (IS_DEAD(this.pass_target)) + || (this.pass_target.flagcarried) + || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius)) + || ((trace_fraction < 1) && (trace_ent != this.pass_target)) + || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit)) + { + // give up, pass failed + ctf_Handle_Drop(this, NULL, DROP_PASS); + } + else + { + // still a viable target, go for it + ctf_CalculatePassVelocity(this, targ_origin, this.origin, true); + } + return; + } + + default: // this should never happen + { + LOG_TRACE("ctf_FlagThink(): Flag exists with no status?"); + return; + } + } +} + +METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher)) +{ + return = false; + if(game_stopped) return; + if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; } + + bool is_not_monster = (!IS_MONSTER(toucher)); + + // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces + if(ITEM_TOUCH_NEEDKILL()) + { + if(!autocvar_g_ctf_flag_return_damage_delay) + { + flag.health = 0; + ctf_CheckFlagReturn(flag, RETURN_NEEDKILL); + } + if(!flag.ctf_flagdamaged_byworld) { return; } + } + + // special touch behaviors + if(STAT(FROZEN, toucher)) { return; } + else if(IS_VEHICLE(toucher)) + { + if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner) + toucher = toucher.owner; // the player is actually the vehicle owner, not other + else + return; // do nothing + } + else if(IS_MONSTER(toucher)) + { + if(!autocvar_g_ctf_allow_monster_touch) + return; // do nothing + } + else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world + { + if(time > flag.wait) // if we haven't in a while, play a sound/effect + { + Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1); + _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM); + flag.wait = time + FLAG_TOUCHRATE; + } + return; + } + else if(IS_DEAD(toucher)) { return; } + + switch(flag.ctf_status) + { + case FLAG_BASE: + { + if(ctf_oneflag) + { + if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster) + ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base + else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster) + ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag + } + else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster) + ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base + else if(CTF_DIFFTEAM(toucher, flag) && (toucher.flagcarried) && CTF_SAMETEAM(toucher.flagcarried, toucher) && (!toucher.ctf_captureshielded) && autocvar_g_ctf_flag_return_carrying && (time > toucher.next_take_time) && is_not_monster) + { + ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag + ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag + } + else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster) + ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag + break; + } + + case FLAG_DROPPED: + { + if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher)) + ctf_Handle_Return(flag, toucher); // toucher just returned his own flag + else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay))) + ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag + break; + } + + case FLAG_CARRY: + { + LOG_TRACE("Someone touched a flag even though it was being carried?"); + break; + } + + case FLAG_PASSING: + { + if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender)) + { + if(DIFF_TEAM(toucher, flag.pass_sender)) + { + if(ctf_Immediate_Return_Allowed(flag, toucher)) + ctf_Handle_Return(flag, toucher); + else if(is_not_monster && (!toucher.flagcarried)) + ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); + } + else if(!toucher.flagcarried) + ctf_Handle_Retrieve(flag, toucher); + } + break; + } + } +} + +.float last_respawn; +void ctf_RespawnFlag(entity flag) +{ + // check for flag respawn being called twice in a row + if(flag.last_respawn > time - 0.5) + { backtrace("flag respawn called twice quickly! please notify Samual about this..."); } + + flag.last_respawn = time; + + // reset the player (if there is one) + if((flag.owner) && (flag.owner.flagcarried == flag)) + { + WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier); + WaypointSprite_Kill(flag.owner.wps_flagreturn); + WaypointSprite_Kill(flag.wps_flagcarrier); + + flag.owner.flagcarried = NULL; + GameRules_scoring_vip(flag.owner, false); + + if(flag.speedrunning) + ctf_FakeTimeLimit(flag.owner, -1); + } + + if((flag.owner) && (flag.owner.vehicle)) + flag.scale = FLAG_SCALE; + + if(flag.ctf_status == FLAG_DROPPED) + { WaypointSprite_Kill(flag.wps_flagdropped); } + + // reset the flag + setattachment(flag, NULL, ""); + setorigin(flag, flag.ctf_spawnorigin); + + set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS)); + flag.takedamage = DAMAGE_NO; + flag.health = flag.max_flag_health; + flag.solid = SOLID_TRIGGER; + flag.velocity = '0 0 0'; + flag.angles = flag.mangle; + flag.flags = FL_ITEM | FL_NOTARGET; + + flag.ctf_status = FLAG_BASE; + flag.owner = NULL; + flag.pass_distance = 0; + flag.pass_sender = NULL; + flag.pass_target = NULL; + flag.ctf_dropper = NULL; + flag.ctf_pickuptime = 0; + flag.ctf_droptime = 0; + flag.ctf_flagdamaged_byworld = false; + navigation_dynamicgoal_unset(flag); + + ctf_CheckStalemate(); +} + +void ctf_Reset(entity this) +{ + if(this.owner && IS_PLAYER(this.owner)) + ctf_Handle_Throw(this.owner, NULL, DROP_RESET); + + this.enemy = NULL; + ctf_RespawnFlag(this); +} + +bool ctf_FlagBase_Customize(entity this, entity client) +{ + entity e = WaypointSprite_getviewentity(client); + entity wp_owner = this.owner; + entity flag = e.flagcarried; + if(flag && CTF_SAMETEAM(e, flag)) + return false; + if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt) + return false; + return true; +} + +void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup() +{ + // bot waypoints + waypoint_spawnforitem_force(this, this.origin); + navigation_dynamicgoal_init(this, true); + + // waypointsprites + entity basename; + switch (this.team) + { + case NUM_TEAM_1: basename = WP_FlagBaseRed; break; + case NUM_TEAM_2: basename = WP_FlagBaseBlue; break; + case NUM_TEAM_3: basename = WP_FlagBaseYellow; break; + case NUM_TEAM_4: basename = WP_FlagBasePink; break; + default: basename = WP_FlagBaseNeutral; break; + } + + entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG); + wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1'); + WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1')); + setcefc(wp, ctf_FlagBase_Customize); + + // captureshield setup + ctf_CaptureShield_Spawn(this); +} + +.bool pushable; + +void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc +{ + // main setup + flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist + ctf_worldflaglist = flag; + + setattachment(flag, NULL, ""); + + flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber))); + flag.team = teamnumber; + flag.classname = "item_flag_team"; + flag.target = "###item###"; // wut? + flag.flags = FL_ITEM | FL_NOTARGET; + IL_PUSH(g_items, flag); + flag.solid = SOLID_TRIGGER; + flag.takedamage = DAMAGE_NO; + flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale; + flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100); + flag.health = flag.max_flag_health; + flag.event_damage = ctf_FlagDamage; + flag.pushable = true; + flag.teleportable = TELEPORT_NORMAL; + flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP; + flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable; + flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable; + if(flag.damagedbycontents) + IL_PUSH(g_damagedbycontents, flag); + flag.velocity = '0 0 0'; + flag.mangle = flag.angles; + flag.reset = ctf_Reset; + settouch(flag, ctf_FlagTouch); + setthink(flag, ctf_FlagThink); + flag.nextthink = time + FLAG_THINKRATE; + flag.ctf_status = FLAG_BASE; + + // crudely force them all to 0 + if(autocvar_g_ctf_score_ignore_fields) + flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0; + + string teamname = Static_Team_ColorName_Lower(teamnumber); + // appearence + if(!flag.scale) { flag.scale = FLAG_SCALE; } + if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); } + if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); } + if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; } + if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; } + if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; } + + // sounds +#define X(s,b) \ + if(flag.s == "") flag.s = b; \ + precache_sound(flag.s); + + X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber)))) + X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber)))) + X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber)))) + X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber)))) + X(snd_flag_respawn, strzone(SND(CTF_RESPAWN))) + X(snd_flag_touch, strzone(SND(CTF_TOUCH))) + X(snd_flag_pass, strzone(SND(CTF_PASS))) +#undef X + + // precache + precache_model(flag.model); + + // appearence + _setmodel(flag, flag.model); // precision set below + setsize(flag, CTF_FLAG.m_mins * flag.scale, CTF_FLAG.m_maxs * flag.scale); + flag.m_mins = flag.mins; // store these for squash checks + flag.m_maxs = flag.maxs; + setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET)); + + if(autocvar_g_ctf_flag_glowtrails) + { + switch(teamnumber) + { + case NUM_TEAM_1: flag.glow_color = 251; break; + case NUM_TEAM_2: flag.glow_color = 210; break; + case NUM_TEAM_3: flag.glow_color = 110; break; + case NUM_TEAM_4: flag.glow_color = 145; break; + default: flag.glow_color = 254; break; + } + flag.glow_size = 25; + flag.glow_trail = 1; + } + + flag.effects |= EF_LOWPRECISION; + if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; } + if(autocvar_g_ctf_dynamiclights) + { + switch(teamnumber) + { + case NUM_TEAM_1: flag.effects |= EF_RED; break; + case NUM_TEAM_2: flag.effects |= EF_BLUE; break; + case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break; + case NUM_TEAM_4: flag.effects |= EF_RED; break; + default: flag.effects |= EF_DIMLIGHT; break; + } + } + + // flag placement + if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location + { + flag.dropped_origin = flag.origin; + flag.noalign = true; + set_movetype(flag, MOVETYPE_NONE); + } + else // drop to floor, automatically find a platform and set that as spawn origin + { + flag.noalign = false; + droptofloor(flag); + set_movetype(flag, MOVETYPE_NONE); + } + + InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION); +} + + +// ================ +// Bot player logic +// ================ + +// NOTE: LEGACY CODE, needs to be re-written! + +void havocbot_ctf_calculate_middlepoint() +{ + entity f; + vector s = '0 0 0'; + vector fo = '0 0 0'; + int n = 0; + + f = ctf_worldflaglist; + while (f) + { + fo = f.origin; + s = s + fo; + f = f.ctf_worldflagnext; + n++; + } + if(!n) + return; + + havocbot_middlepoint = s / n; + havocbot_middlepoint_radius = vlen(fo - havocbot_middlepoint); + + havocbot_symmetryaxis_equation = '0 0 0'; + if(n == 2) + { + // for symmetrical editing of waypoints + entity f1 = ctf_worldflaglist; + entity f2 = f1.ctf_worldflagnext; + float m = -(f1.origin.y - f2.origin.y) / (f1.origin.x - f2.origin.x); + float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x; + havocbot_symmetryaxis_equation.x = m; + havocbot_symmetryaxis_equation.y = q; + } + // store number of flags in this otherwise unused vector component + havocbot_symmetryaxis_equation.z = n; +} + + +entity havocbot_ctf_find_flag(entity bot) +{ + entity f; + f = ctf_worldflaglist; + while (f) + { + if (CTF_SAMETEAM(bot, f)) + return f; + f = f.ctf_worldflagnext; + } + return NULL; +} + +entity havocbot_ctf_find_enemy_flag(entity bot) +{ + entity f; + f = ctf_worldflaglist; + while (f) + { + if(ctf_oneflag) + { + if(CTF_DIFFTEAM(bot, f)) + { + if(f.team) + { + if(bot.flagcarried) + return f; + } + else if(!bot.flagcarried) + return f; + } + } + else if (CTF_DIFFTEAM(bot, f)) + return f; + f = f.ctf_worldflagnext; + } + return NULL; +} + +int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius) +{ + if (!teamplay) + return 0; + + int c = 0; + + FOREACH_CLIENT(IS_PLAYER(it), { + if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot) + continue; + + if(vdist(it.origin - org, <, tc_radius)) + ++c; + }); + + return c; +} + +// unused +#if 0 +void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale) +{ + entity head; + head = ctf_worldflaglist; + while (head) + { + if (CTF_SAMETEAM(this, head)) + break; + head = head.ctf_worldflagnext; + } + if (head) + navigation_routerating(this, head, ratingscale, 10000); +} +#endif + +void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale) +{ + entity head; + head = ctf_worldflaglist; + while (head) + { + if (CTF_SAMETEAM(this, head)) + { + if (this.flagcarried) + if ((this.flagcarried.cnt || head.cnt) && this.flagcarried.cnt != head.cnt) + { + head = head.ctf_worldflagnext; // skip base if it has a different group + continue; + } + break; + } + head = head.ctf_worldflagnext; + } + if (!head) + return; + + navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000); +} + +void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale) +{ + entity head; + head = ctf_worldflaglist; + while (head) + { + if(ctf_oneflag) + { + if(CTF_DIFFTEAM(this, head)) + { + if(head.team) + { + if(this.flagcarried) + break; + } + else if(!this.flagcarried) + break; + } + } + else if(CTF_DIFFTEAM(this, head)) + break; + head = head.ctf_worldflagnext; + } + if (head) + navigation_routerating(this, head, ratingscale, 10000); +} + +void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale) +{ + if (!bot_waypoints_for_items) + { + havocbot_goalrating_ctf_enemyflag(this, ratingscale); + return; + } + + entity head; + + head = havocbot_ctf_find_enemy_flag(this); + + if (!head) + return; + + navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000); +} + +void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale) +{ + entity mf; + + mf = havocbot_ctf_find_flag(this); + + if(mf.ctf_status == FLAG_BASE) + return; + + if(mf.tag_entity) + navigation_routerating(this, mf.tag_entity, ratingscale, 10000); +} + +void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius) +{ + entity head; + head = ctf_worldflaglist; + while (head) + { + // flag is out in the field + if(head.ctf_status != FLAG_BASE) + if(head.tag_entity==NULL) // dropped + { + if(df_radius) + { + if(vdist(org - head.origin, <, df_radius)) + navigation_routerating(this, head, ratingscale, 10000); + } + else + navigation_routerating(this, head, ratingscale, 10000); + } + + head = head.ctf_worldflagnext; + } +} + +void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius) +{ + IL_EACH(g_items, it.bot_pickup, + { + // gather health and armor only + if (it.solid) + if (it.health || it.armorvalue) + if (vdist(it.origin - org, <, sradius)) + { + // get the value of the item + float t = it.bot_pickupevalfunc(this, it) * 0.0001; + if (t > 0) + navigation_routerating(this, it, t * ratingscale, 500); + } + }); +} + +void havocbot_ctf_reset_role(entity this) +{ + float cdefense, cmiddle, coffense; + entity mf, ef; + float c; + + if(IS_DEAD(this)) + return; + + // Check ctf flags + if (this.flagcarried) + { + havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER); + return; + } + + mf = havocbot_ctf_find_flag(this); + ef = havocbot_ctf_find_enemy_flag(this); + + // Retrieve stolen flag + if(mf.ctf_status!=FLAG_BASE) + { + havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER); + return; + } + + // If enemy flag is taken go to the middle to intercept pursuers + if(ef.ctf_status!=FLAG_BASE) + { + havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE); + return; + } + + // if there is only me on the team switch to offense + c = 0; + FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), { ++c; }); + + if(c==1) + { + havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE); + return; + } + + // Evaluate best position to take + // Count mates on middle position + cmiddle = havocbot_ctf_teamcount(this, havocbot_middlepoint, havocbot_middlepoint_radius * 0.5); + + // Count mates on defense position + cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_middlepoint_radius * 0.5); + + // Count mates on offense position + coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_middlepoint_radius); + + if(cdefense<=coffense) + havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE); + else if(coffense<=cmiddle) + havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE); + else + havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE); +} + +void havocbot_role_ctf_carrier(entity this) +{ + if(IS_DEAD(this)) + { + havocbot_ctf_reset_role(this); + return; + } + + if (this.flagcarried == NULL) + { + havocbot_ctf_reset_role(this); + return; + } + + if (navigation_goalrating_timeout(this)) + { + navigation_goalrating_start(this); + + if(ctf_oneflag) + havocbot_goalrating_ctf_enemybase(this, 50000); + else + havocbot_goalrating_ctf_ourbase(this, 50000); + + if(this.health<100) + havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000); + + navigation_goalrating_end(this); + + navigation_goalrating_timeout_set(this); + + entity head = ctf_worldflaglist; + while (head) + { + if (this.goalentity == head.bot_basewaypoint) + { + this.goalentity_lock_timeout = time + 5; + break; + } + head = head.ctf_worldflagnext; + } + + if (this.goalentity) + this.havocbot_cantfindflag = time + 10; + else if (time > this.havocbot_cantfindflag) + { + // Can't navigate to my own base, suicide! + // TODO: drop it and wander around + Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0'); + return; + } + } +} + +void havocbot_role_ctf_escort(entity this) +{ + entity mf, ef; + + if(IS_DEAD(this)) + { + havocbot_ctf_reset_role(this); + return; + } + + if (this.flagcarried) + { + havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER); + return; + } + + // If enemy flag is back on the base switch to previous role + ef = havocbot_ctf_find_enemy_flag(this); + if(ef.ctf_status==FLAG_BASE) + { + this.havocbot_role = this.havocbot_previous_role; + this.havocbot_role_timeout = 0; + return; + } + + // If the flag carrier reached the base switch to defense + mf = havocbot_ctf_find_flag(this); + if(mf.ctf_status!=FLAG_BASE) + if(vdist(ef.origin - mf.dropped_origin, <, 300)) + { + havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE); + return; + } + + // Set the role timeout if necessary + if (!this.havocbot_role_timeout) + { + this.havocbot_role_timeout = time + random() * 30 + 60; + } + + // If nothing happened just switch to previous role + if (time > this.havocbot_role_timeout) + { + this.havocbot_role = this.havocbot_previous_role; + this.havocbot_role_timeout = 0; + return; + } + + // Chase the flag carrier + if (navigation_goalrating_timeout(this)) + { + navigation_goalrating_start(this); + + havocbot_goalrating_ctf_enemyflag(this, 30000); + havocbot_goalrating_ctf_ourstolenflag(this, 40000); + havocbot_goalrating_items(this, 10000, this.origin, 10000); + + navigation_goalrating_end(this); + + navigation_goalrating_timeout_set(this); + } +} + +void havocbot_role_ctf_offense(entity this) +{ + entity mf, ef; + vector pos; + + if(IS_DEAD(this)) + { + havocbot_ctf_reset_role(this); + return; + } + + if (this.flagcarried) + { + havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER); + return; + } + + // Check flags + mf = havocbot_ctf_find_flag(this); + ef = havocbot_ctf_find_enemy_flag(this); + + // Own flag stolen + if(mf.ctf_status!=FLAG_BASE) + { + if(mf.tag_entity) + pos = mf.tag_entity.origin; + else + pos = mf.origin; + + // Try to get it if closer than the enemy base + if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos)) + { + havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER); + return; + } + } + + // Escort flag carrier + if(ef.ctf_status!=FLAG_BASE) + { + if(ef.tag_entity) + pos = ef.tag_entity.origin; + else + pos = ef.origin; + + if(vdist(pos - mf.dropped_origin, >, 700)) + { + havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT); + return; + } + } + + // About to fail, switch to middlefield + if(this.health<50) + { + havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE); + return; + } + + // Set the role timeout if necessary + if (!this.havocbot_role_timeout) + this.havocbot_role_timeout = time + 120; + + if (time > this.havocbot_role_timeout) + { + havocbot_ctf_reset_role(this); + return; + } + + if (navigation_goalrating_timeout(this)) + { + navigation_goalrating_start(this); + + havocbot_goalrating_ctf_ourstolenflag(this, 50000); + havocbot_goalrating_ctf_enemybase(this, 20000); + havocbot_goalrating_items(this, 5000, this.origin, 1000); + havocbot_goalrating_items(this, 1000, this.origin, 10000); + + navigation_goalrating_end(this); + + navigation_goalrating_timeout_set(this); + } +} + +// Retriever (temporary role): +void havocbot_role_ctf_retriever(entity this) +{ + entity mf; + + if(IS_DEAD(this)) + { + havocbot_ctf_reset_role(this); + return; + } + + if (this.flagcarried) + { + havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER); + return; + } + + // If flag is back on the base switch to previous role + mf = havocbot_ctf_find_flag(this); + if(mf.ctf_status==FLAG_BASE) + { + if (mf.enemy == this) // did this bot return the flag? + navigation_goalrating_timeout_force(this); + havocbot_ctf_reset_role(this); + return; + } + + if (!this.havocbot_role_timeout) + this.havocbot_role_timeout = time + 20; + + if (time > this.havocbot_role_timeout) + { + havocbot_ctf_reset_role(this); + return; + } + + if (navigation_goalrating_timeout(this)) + { + float rt_radius; + rt_radius = 10000; + + navigation_goalrating_start(this); + + havocbot_goalrating_ctf_ourstolenflag(this, 50000); + havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius); + havocbot_goalrating_ctf_enemybase(this, 30000); + havocbot_goalrating_items(this, 500, this.origin, rt_radius); + + navigation_goalrating_end(this); + + navigation_goalrating_timeout_set(this); + } +} + +void havocbot_role_ctf_middle(entity this) +{ + entity mf; + + if(IS_DEAD(this)) + { + havocbot_ctf_reset_role(this); + return; + } + + if (this.flagcarried) + { + havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER); + return; + } + + mf = havocbot_ctf_find_flag(this); + if(mf.ctf_status!=FLAG_BASE) + { + havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER); + return; + } + + if (!this.havocbot_role_timeout) + this.havocbot_role_timeout = time + 10; + + if (time > this.havocbot_role_timeout) + { + havocbot_ctf_reset_role(this); + return; + } + + if (navigation_goalrating_timeout(this)) + { + vector org; + + org = havocbot_middlepoint; + org.z = this.origin.z; + + navigation_goalrating_start(this); + + havocbot_goalrating_ctf_ourstolenflag(this, 50000); + havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000); + havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5); + havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5); + havocbot_goalrating_items(this, 2500, this.origin, 10000); + havocbot_goalrating_ctf_enemybase(this, 2500); + + navigation_goalrating_end(this); + + navigation_goalrating_timeout_set(this); + } +} + +void havocbot_role_ctf_defense(entity this) +{ + entity mf; + + if(IS_DEAD(this)) + { + havocbot_ctf_reset_role(this); + return; + } + + if (this.flagcarried) + { + havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER); + return; + } + + // If own flag was captured + mf = havocbot_ctf_find_flag(this); + if(mf.ctf_status!=FLAG_BASE) + { + havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER); + return; + } + + if (!this.havocbot_role_timeout) + this.havocbot_role_timeout = time + 30; + + if (time > this.havocbot_role_timeout) + { + havocbot_ctf_reset_role(this); + return; + } + if (navigation_goalrating_timeout(this)) + { + vector org = mf.dropped_origin; + + navigation_goalrating_start(this); + + // if enemies are closer to our base, go there + entity closestplayer = NULL; + float distance, bestdistance = 10000; + FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), { + distance = vlen(org - it.origin); + if(distance, 1000)) + if(checkpvs(this.origin,closestplayer)||random()<0.5) + havocbot_goalrating_ctf_ourbase(this, 30000); + + havocbot_goalrating_ctf_ourstolenflag(this, 20000); + havocbot_goalrating_ctf_droppedflags(this, 20000, org, havocbot_middlepoint_radius); + havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius); + havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius); + havocbot_goalrating_items(this, 5000, this.origin, 10000); + + navigation_goalrating_end(this); + + navigation_goalrating_timeout_set(this); + } +} + +void havocbot_role_ctf_setrole(entity bot, int role) +{ + string s = "(null)"; + switch(role) + { + case HAVOCBOT_CTF_ROLE_CARRIER: + s = "carrier"; + bot.havocbot_role = havocbot_role_ctf_carrier; + bot.havocbot_role_timeout = 0; + bot.havocbot_cantfindflag = time + 10; + if (bot.havocbot_previous_role != bot.havocbot_role) + navigation_goalrating_timeout_force(bot); + break; + case HAVOCBOT_CTF_ROLE_DEFENSE: + s = "defense"; + bot.havocbot_role = havocbot_role_ctf_defense; + bot.havocbot_role_timeout = 0; + break; + case HAVOCBOT_CTF_ROLE_MIDDLE: + s = "middle"; + bot.havocbot_role = havocbot_role_ctf_middle; + bot.havocbot_role_timeout = 0; + break; + case HAVOCBOT_CTF_ROLE_OFFENSE: + s = "offense"; + bot.havocbot_role = havocbot_role_ctf_offense; + bot.havocbot_role_timeout = 0; + break; + case HAVOCBOT_CTF_ROLE_RETRIEVER: + s = "retriever"; + bot.havocbot_previous_role = bot.havocbot_role; + bot.havocbot_role = havocbot_role_ctf_retriever; + bot.havocbot_role_timeout = time + 10; + if (bot.havocbot_previous_role != bot.havocbot_role) + navigation_goalrating_timeout_expire(bot, 2); + break; + case HAVOCBOT_CTF_ROLE_ESCORT: + s = "escort"; + bot.havocbot_previous_role = bot.havocbot_role; + bot.havocbot_role = havocbot_role_ctf_escort; + bot.havocbot_role_timeout = time + 30; + if (bot.havocbot_previous_role != bot.havocbot_role) + navigation_goalrating_timeout_expire(bot, 2); + break; + } + LOG_TRACE(bot.netname, " switched to ", s); +} + + +// ============== +// Hook Functions +// ============== + +MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink) +{ + entity player = M_ARGV(0, entity); + + int t = 0, t2 = 0, t3 = 0; + bool b1 = false, b2 = false, b3 = false, b4 = false, b5 = false; // TODO: kill this, we WANT to show the other flags, somehow! (note: also means you don't see if you're FC) + + // initially clear items so they can be set as necessary later. + STAT(CTF_FLAGSTATUS, player) &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST + | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST + | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST + | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST + | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST + | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE); + + // scan through all the flags and notify the client about them + for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) + { + if(flag.team == NUM_TEAM_1 && !b1) { b1 = true; t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; } + if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; } + if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; } + if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; } + if(flag.team == 0 && !b5) { b5 = true; t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; STAT(CTF_FLAGSTATUS, player) |= CTF_FLAG_NEUTRAL; } + + switch(flag.ctf_status) + { + case FLAG_PASSING: + case FLAG_CARRY: + { + if((flag.owner == player) || (flag.pass_sender == player)) + STAT(CTF_FLAGSTATUS, player) |= t; // carrying: player is currently carrying the flag + else + STAT(CTF_FLAGSTATUS, player) |= t2; // taken: someone else is carrying the flag + break; + } + case FLAG_DROPPED: + { + STAT(CTF_FLAGSTATUS, player) |= t3; // lost: the flag is dropped somewhere on the map + break; + } + } + } + + // item for stopping players from capturing the flag too often + if(player.ctf_captureshielded) + STAT(CTF_FLAGSTATUS, player) |= CTF_SHIELDED; + + if(ctf_stalemate) + STAT(CTF_FLAGSTATUS, player) |= CTF_STALEMATE; + + // update the health of the flag carrier waypointsprite + if(player.wps_flagcarrier) + WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id)); +} + +MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + float frag_damage = M_ARGV(4, float); + vector frag_force = M_ARGV(6, vector); + + if(frag_attacker.flagcarried) // if the attacker is a flagcarrier + { + if(frag_target == frag_attacker) // damage done to yourself + { + frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor; + frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor; + } + else // damage done to everyone else + { + frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor; + frag_force *= autocvar_g_ctf_flagcarrier_forcefactor; + } + + M_ARGV(4, float) = frag_damage; + M_ARGV(6, vector) = frag_force; + } + else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier + { + if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id))) + if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time) + { + frag_target.wps_helpme_time = time; + WaypointSprite_HelpMePing(frag_target.wps_flagcarrier); + } + // todo: add notification for when flag carrier needs help? + } +} + +MUTATOR_HOOKFUNCTION(ctf, PlayerDies) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + + if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried)) + { + GameRules_scoring_add_team(frag_attacker, SCORE, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill)); + GameRules_scoring_add(frag_attacker, CTF_FCKILLS, 1); + } + + if(frag_target.flagcarried) + { + entity tmp_entity = frag_target.flagcarried; + ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL); + tmp_entity.ctf_dropper = NULL; + } +} + +MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill) +{ + M_ARGV(2, float) = 0; // frag score + return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true +} + +void ctf_RemovePlayer(entity player) +{ + if(player.flagcarried) + { ctf_Handle_Throw(player, NULL, DROP_NORMAL); } + + for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) + { + if(flag.pass_sender == player) { flag.pass_sender = NULL; } + if(flag.pass_target == player) { flag.pass_target = NULL; } + if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; } + } +} + +MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver) +{ + entity player = M_ARGV(0, entity); + + ctf_RemovePlayer(player); +} + +MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect) +{ + entity player = M_ARGV(0, entity); + + ctf_RemovePlayer(player); +} + +MUTATOR_HOOKFUNCTION(ctf, ClientConnect) +{ + if(!autocvar_g_ctf_leaderboard) + return; + + entity player = M_ARGV(0, entity); + + if(IS_REAL_CLIENT(player)) + { + int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt); + race_send_rankings_cnt(MSG_ONE); + for (int i = 1; i <= m; ++i) + { + race_SendRankings(i, 0, 0, MSG_ONE); + } + } +} + +MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys) +{ + if(!autocvar_g_ctf_leaderboard) + return; + + entity player = M_ARGV(0, entity); + + if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1) + { + if (!player.stored_netname) + player.stored_netname = strzone(uid2name(player.crypto_idfp)); + if(player.stored_netname != player.netname) + { + db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname); + strcpy(player.stored_netname, player.netname); + } + } +} + +MUTATOR_HOOKFUNCTION(ctf, PortalTeleport) +{ + entity player = M_ARGV(0, entity); + + if(player.flagcarried) + if(!autocvar_g_ctf_portalteleport) + { ctf_Handle_Throw(player, NULL, DROP_NORMAL); } +} + +MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey) +{ + if(MUTATOR_RETURNVALUE || game_stopped) return; + + entity player = M_ARGV(0, entity); + + if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch)) + { + // pass the flag to a team mate + if(autocvar_g_ctf_pass) + { + entity head, closest_target = NULL; + head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true); + + while(head) // find the closest acceptable target to pass to + { + if(IS_PLAYER(head) && !IS_DEAD(head)) + if(head != player && SAME_TEAM(head, player)) + if(!head.speedrunning && !head.vehicle) + { + // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) + vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head)); + vector passer_center = CENTER_OR_VIEWOFS(player); + + if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest)) + { + if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried) + { + if(IS_BOT_CLIENT(head)) + { + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname); + ctf_Handle_Throw(head, player, DROP_PASS); + } + else + { + Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname); + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname); + } + player.throw_antispam = time + autocvar_g_ctf_pass_wait; + return true; + } + else if(player.flagcarried && !head.flagcarried) + { + if(closest_target) + { + vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target)); + if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center)) + { closest_target = head; } + } + else { closest_target = head; } + } + } + } + head = head.chain; + } + + if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; } + } + + // throw the flag in front of you + if(autocvar_g_ctf_throw && player.flagcarried) + { + if(player.throw_count == -1) + { + if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) + { + player.throw_prevtime = time; + player.throw_count = 1; + ctf_Handle_Throw(player, NULL, DROP_THROW); + return true; + } + else + { + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time)); + return false; + } + } + else + { + if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; } + else { player.throw_count += 1; } + if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; } + + player.throw_prevtime = time; + ctf_Handle_Throw(player, NULL, DROP_THROW); + return true; + } + } + } +} + +MUTATOR_HOOKFUNCTION(ctf, HelpMePing) +{ + entity player = M_ARGV(0, entity); + + if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification + { + player.wps_helpme_time = time; + WaypointSprite_HelpMePing(player.wps_flagcarrier); + } + else // create a normal help me waypointsprite + { + WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME); + WaypointSprite_Ping(player.wps_helpme); + } + + return true; +} + +MUTATOR_HOOKFUNCTION(ctf, VehicleEnter) +{ + entity player = M_ARGV(0, entity); + entity veh = M_ARGV(1, entity); + + if(player.flagcarried) + { + if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch) + { + ctf_Handle_Throw(player, NULL, DROP_NORMAL); + } + else + { + player.flagcarried.nodrawtoclient = player; // hide the flag from the driver + setattachment(player.flagcarried, veh, ""); + setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET); + player.flagcarried.scale = VEHICLE_FLAG_SCALE; + //player.flagcarried.angles = '0 0 0'; + } + return true; + } +} + +MUTATOR_HOOKFUNCTION(ctf, VehicleExit) +{ + entity player = M_ARGV(0, entity); + + if(player.flagcarried) + { + setattachment(player.flagcarried, player, ""); + setorigin(player.flagcarried, FLAG_CARRY_OFFSET); + player.flagcarried.scale = FLAG_SCALE; + player.flagcarried.angles = '0 0 0'; + player.flagcarried.nodrawtoclient = NULL; + return true; + } +} + +MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun) +{ + entity player = M_ARGV(0, entity); + + if(player.flagcarried) + { + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN)); + ctf_RespawnFlag(player.flagcarried); + return true; + } +} + +MUTATOR_HOOKFUNCTION(ctf, MatchEnd) +{ + entity flag; // temporary entity for the search method + + for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) + { + switch(flag.ctf_status) + { + case FLAG_DROPPED: + case FLAG_PASSING: + { + // lock the flag, game is over + set_movetype(flag, MOVETYPE_NONE); + flag.takedamage = DAMAGE_NO; + flag.solid = SOLID_NOT; + flag.nextthink = false; // stop thinking + + //dprint("stopping the ", flag.netname, " from moving.\n"); + break; + } + + default: + case FLAG_BASE: + case FLAG_CARRY: + { + // do nothing for these flags + break; + } + } + } +} + +MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole) +{ + entity bot = M_ARGV(0, entity); + + havocbot_ctf_reset_role(bot); + return true; +} + +MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams) +{ + //M_ARGV(0, float) = ctf_teams; + M_ARGV(1, string) = "ctf_team"; + return true; +} + +MUTATOR_HOOKFUNCTION(ctf, SpectateCopy) +{ + entity spectatee = M_ARGV(0, entity); + entity client = M_ARGV(1, entity); + + STAT(CTF_FLAGSTATUS, client) = STAT(CTF_FLAGSTATUS, spectatee); +} + +MUTATOR_HOOKFUNCTION(ctf, GetRecords) +{ + int record_page = M_ARGV(0, int); + string ret_string = M_ARGV(1, string); + + for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i) + { + if (MapInfo_Get_ByID(i)) + { + float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time"))); + + if(!r) + continue; + + // TODO: uid2name + string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname")); + ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n"); + } + } + + M_ARGV(1, string) = ret_string; +} + +bool superspec_Spectate(entity this, entity targ); // TODO +void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO +MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand) +{ + entity player = M_ARGV(0, entity); + string cmd_name = M_ARGV(1, string); + int cmd_argc = M_ARGV(2, int); + + if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; } + + if(cmd_name == "followfc") + { + if(!g_ctf) + return true; + + int _team = 0; + bool found = false; + + if(cmd_argc == 2) + { + switch(argv(1)) + { + case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break; + case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break; + case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break; + case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break; + } + } + + FOREACH_CLIENT(IS_PLAYER(it), { + if(it.flagcarried && (it.team == _team || _team == 0)) + { + found = true; + if(_team == 0 && IS_SPEC(player) && player.enemy == it) + continue; // already spectating this fc, try another + return superspec_Spectate(player, it); + } + }); + + if(!found) + superspec_msg("", "", player, "No active flag carrier\n", 1); + return true; + } +} + +MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems) +{ + entity frag_target = M_ARGV(0, entity); + + if(frag_target.flagcarried) + ctf_Handle_Throw(frag_target, NULL, DROP_THROW); +} + + +// ========== +// Spawnfuncs +// ========== + +/*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37) +CTF flag for team one (Red). +Keys: +"angle" Angle the flag will point (minus 90 degrees)... +"model" model to use, note this needs red and blue as skins 0 and 1... +"noise" sound played when flag is picked up... +"noise1" sound played when flag is returned by a teammate... +"noise2" sound played when flag is captured... +"noise3" sound played when flag is lost in the field and respawns itself... +"noise4" sound played when flag is dropped by a player... +"noise5" sound played when flag touches the ground... */ +spawnfunc(item_flag_team1) +{ + if(!g_ctf) { delete(this); return; } + + ctf_FlagSetup(NUM_TEAM_1, this); +} + +/*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37) +CTF flag for team two (Blue). +Keys: +"angle" Angle the flag will point (minus 90 degrees)... +"model" model to use, note this needs red and blue as skins 0 and 1... +"noise" sound played when flag is picked up... +"noise1" sound played when flag is returned by a teammate... +"noise2" sound played when flag is captured... +"noise3" sound played when flag is lost in the field and respawns itself... +"noise4" sound played when flag is dropped by a player... +"noise5" sound played when flag touches the ground... */ +spawnfunc(item_flag_team2) +{ + if(!g_ctf) { delete(this); return; } + + ctf_FlagSetup(NUM_TEAM_2, this); +} + +/*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37) +CTF flag for team three (Yellow). +Keys: +"angle" Angle the flag will point (minus 90 degrees)... +"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3... +"noise" sound played when flag is picked up... +"noise1" sound played when flag is returned by a teammate... +"noise2" sound played when flag is captured... +"noise3" sound played when flag is lost in the field and respawns itself... +"noise4" sound played when flag is dropped by a player... +"noise5" sound played when flag touches the ground... */ +spawnfunc(item_flag_team3) +{ + if(!g_ctf) { delete(this); return; } + + ctf_FlagSetup(NUM_TEAM_3, this); +} + +/*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37) +CTF flag for team four (Pink). +Keys: +"angle" Angle the flag will point (minus 90 degrees)... +"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3... +"noise" sound played when flag is picked up... +"noise1" sound played when flag is returned by a teammate... +"noise2" sound played when flag is captured... +"noise3" sound played when flag is lost in the field and respawns itself... +"noise4" sound played when flag is dropped by a player... +"noise5" sound played when flag touches the ground... */ +spawnfunc(item_flag_team4) +{ + if(!g_ctf) { delete(this); return; } + + ctf_FlagSetup(NUM_TEAM_4, this); +} + +/*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37) +CTF flag (Neutral). +Keys: +"angle" Angle the flag will point (minus 90 degrees)... +"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3... +"noise" sound played when flag is picked up... +"noise1" sound played when flag is returned by a teammate... +"noise2" sound played when flag is captured... +"noise3" sound played when flag is lost in the field and respawns itself... +"noise4" sound played when flag is dropped by a player... +"noise5" sound played when flag touches the ground... */ +spawnfunc(item_flag_neutral) +{ + if(!g_ctf) { delete(this); return; } + if(!cvar("g_ctf_oneflag")) { delete(this); return; } + + ctf_FlagSetup(0, this); +} + +/*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32) +Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map. +Note: If you use spawnfunc_ctf_team entities you must define at least 2! However, unlike domination, you don't need to make a blank one too. +Keys: +"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)... +"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */ +spawnfunc(ctf_team) +{ + if(!g_ctf) { delete(this); return; } + + this.classname = "ctf_team"; + this.team = this.cnt + 1; +} + +// compatibility for quake maps +spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); } +spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); } +spawnfunc(info_player_team1); +spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); } +spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); } +spawnfunc(info_player_team2); +spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); } +spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); } + +spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); } +spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); } + +// compatibility for wop maps +spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); } +spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); } +spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); } +spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); } +spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); } +spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); } + + +// ============== +// Initialization +// ============== + +// scoreboard setup +void ctf_ScoreRules(int teams) +{ + CheckAllowedTeams(NULL); + GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, { + field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY); + field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); + field(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME); + field(SP_CTF_PICKUPS, "pickups", 0); + field(SP_CTF_FCKILLS, "fckills", 0); + field(SP_CTF_RETURNS, "returns", 0); + field(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER); + }); +} + +// code from here on is just to support maps that don't have flag and team entities +void ctf_SpawnTeam (string teamname, int teamcolor) +{ + entity this = new_pure(ctf_team); + this.netname = teamname; + this.cnt = teamcolor - 1; + this.spawnfunc_checked = true; + this.team = teamcolor; +} + +void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up. +{ + ctf_teams = 0; + + entity tmp_entity; + for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) + { + //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); } + //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); } + + switch(tmp_entity.team) + { + case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break; + case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break; + case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break; + case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break; + } + if(tmp_entity.team == 0) { ctf_oneflag = true; } + } + + havocbot_ctf_calculate_middlepoint(); + + if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags! + { + ctf_teams = 0; // so set the default red and blue teams + BITSET_ASSIGN(ctf_teams, BIT(0)); + BITSET_ASSIGN(ctf_teams, BIT(1)); + } + + //ctf_teams = bound(2, ctf_teams, 4); + + // if no teams are found, spawn defaults + if(find(NULL, classname, "ctf_team") == NULL) + { + LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway."); + if(ctf_teams & BIT(0)) + ctf_SpawnTeam("Red", NUM_TEAM_1); + if(ctf_teams & BIT(1)) + ctf_SpawnTeam("Blue", NUM_TEAM_2); + if(ctf_teams & BIT(2)) + ctf_SpawnTeam("Yellow", NUM_TEAM_3); + if(ctf_teams & BIT(3)) + ctf_SpawnTeam("Pink", NUM_TEAM_4); + } + + ctf_ScoreRules(ctf_teams); +} + +void ctf_Initialize() +{ + ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"))); + + ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore; + ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio; + ctf_captureshield_force = autocvar_g_ctf_shield_force; + + InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE); +} +#endif diff --git a/qcsrc/common/gamemodes/gamemode/ctf/ctf.qh b/qcsrc/common/gamemodes/gamemode/ctf/ctf.qh new file mode 100644 index 000000000..74a3993e3 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/ctf/ctf.qh @@ -0,0 +1,188 @@ +#pragma once + +#ifdef SVQC + +void ctf_Initialize(); + +REGISTER_MUTATOR(ctf, false) +{ + MUTATOR_STATIC(); + MUTATOR_ONADD + { + GameRules_teams(true); + GameRules_limit_score(autocvar_capturelimit_override); + GameRules_limit_lead(autocvar_captureleadlimit_override); + + ctf_Initialize(); + } + return 0; +} + +// used in cheats.qc +void ctf_RespawnFlag(entity flag); + +// score rule declarations +const int ST_CTF_CAPS = 1; + +CLASS(Flag, Pickup) + ATTRIB(Flag, m_mins, vector, (PL_MIN_CONST + '0 0 -13') * 1.4); // scaling be damned + ATTRIB(Flag, m_maxs, vector, (PL_MAX_CONST + '0 0 -13') * 1.4); +ENDCLASS(Flag) +Flag CTF_FLAG; STATIC_INIT(Flag) { CTF_FLAG = NEW(Flag); } +void ctf_FlagTouch(entity this, entity toucher) { ITEM_HANDLE(Pickup, CTF_FLAG, this, toucher); } + +// flag constants // for most of these, there is just one question to be asked: WHYYYYY? + +const float FLAG_SCALE = 0.6; + +const float FLAG_THINKRATE = 0.2; +const float FLAG_TOUCHRATE = 0.5; +const float WPFE_THINKRATE = 0.5; + +const vector FLAG_DROP_OFFSET = ('0 0 32'); +const vector FLAG_CARRY_OFFSET = ('-16 0 8'); +#define FLAG_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13)) +const vector FLAG_WAYPOINT_OFFSET = ('0 0 64'); +const vector FLAG_FLOAT_OFFSET = ('0 0 32'); +const vector FLAG_PASS_ARC_OFFSET = ('0 0 -10'); + +const vector VEHICLE_FLAG_OFFSET = ('0 0 96'); +const float VEHICLE_FLAG_SCALE = 1.0; + +// waypoint colors +#define WPCOLOR_ENEMYFC(t) ((t) ? colormapPaletteColor(t - 1, false) * 0.75 : '1 1 1') +#define WPCOLOR_FLAGCARRIER(t) (WP_FlagCarrier.m_color) +#define WPCOLOR_DROPPEDFLAG(t) ((t) ? ('0.25 0.25 0.25' + colormapPaletteColor(t - 1, false)) * 0.5 : '1 1 1') + +// sounds +#define snd_flag_taken noise +#define snd_flag_returned noise1 +#define snd_flag_capture noise2 +#define snd_flag_respawn noise3 +.string snd_flag_dropped; +.string snd_flag_touch; +.string snd_flag_pass; + +// score fields +.float score_assist; +.float score_capture; +.float score_drop; // note: negated +.float score_pickup; +.float score_return; +.float score_team_capture; // shouldn't be too high + +// effects +.string toucheffect; +.string passeffect; +.string capeffect; + +// list of flags on the map +entity ctf_worldflaglist; +.entity ctf_worldflagnext; +.entity ctf_staleflagnext; + +// waypoint sprites +.entity wps_helpme; +.entity wps_flagbase; +.entity wps_flagcarrier; +.entity wps_flagdropped; +.entity wps_flagreturn; +.entity wps_enemyflagcarrier; +.float wps_helpme_time; +bool wpforenemy_announced; +float wpforenemy_nextthink; + +// statuses +const int FLAG_BASE = 1; +const int FLAG_DROPPED = 2; +const int FLAG_CARRY = 3; +const int FLAG_PASSING = 4; + +const int DROP_NORMAL = 1; +const int DROP_THROW = 2; +const int DROP_PASS = 3; +const int DROP_RESET = 4; + +const int PICKUP_BASE = 1; +const int PICKUP_DROPPED = 2; + +const int CAPTURE_NORMAL = 1; +const int CAPTURE_DROPPED = 2; + +const int RETURN_TIMEOUT = 1; +const int RETURN_DROPPED = 2; +const int RETURN_DAMAGE = 3; +const int RETURN_SPEEDRUN = 4; +const int RETURN_NEEDKILL = 5; + +bool ctf_Stalemate_Customize(entity this, entity client); + +void ctf_Handle_Throw(entity player, entity receiver, float droptype); + +// flag properties +#define ctf_spawnorigin dropped_origin +bool ctf_stalemate; // indicates that a stalemate is active +float ctf_captimerecord; // record time for capturing the flag +.float ctf_pickuptime; +.float ctf_droptime; +.int ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally) +.entity ctf_dropper; // don't allow spam of dropping the flag +.int max_flag_health; +.float next_take_time; +.bool ctf_flagdamaged_byworld; +int ctf_teams; +.entity enemy; // when flag is back in the base, it remembers last player who carried/touched the flag, useful to bots + +// passing/throwing properties +.float pass_distance; +.entity pass_sender; +.entity pass_target; +.float throw_antispam; +.float throw_prevtime; +.int throw_count; + +// CaptureShield: If the player is too bad to be allowed to capture, shield them from taking the flag. +.bool ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture +float ctf_captureshield_min_negscore; // punish at -20 points +float ctf_captureshield_max_ratio; // punish at most 30% of each team +float ctf_captureshield_force; // push force of the shield + +// 1 flag ctf +bool ctf_oneflag; // indicates whether or not a neutral flag has been found + +// bot player logic +const int HAVOCBOT_CTF_ROLE_NONE = 0; +const int HAVOCBOT_CTF_ROLE_DEFENSE = 2; +const int HAVOCBOT_CTF_ROLE_MIDDLE = 4; +const int HAVOCBOT_CTF_ROLE_OFFENSE = 8; +const int HAVOCBOT_CTF_ROLE_CARRIER = 16; +const int HAVOCBOT_CTF_ROLE_RETRIEVER = 32; +const int HAVOCBOT_CTF_ROLE_ESCORT = 64; + +.bool havocbot_cantfindflag; + +void havocbot_role_ctf_setrole(entity bot, int role); + +// team checking +#define CTF_SAMETEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? DIFF_TEAM(a,b) : SAME_TEAM(a,b)) +#define CTF_DIFFTEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? SAME_TEAM(a,b) : DIFF_TEAM(a,b)) +#endif + +const int CTF_RED_FLAG_TAKEN = 1; +const int CTF_RED_FLAG_LOST = 2; +const int CTF_RED_FLAG_CARRYING = 3; +const int CTF_BLUE_FLAG_TAKEN = 4; +const int CTF_BLUE_FLAG_LOST = 8; +const int CTF_BLUE_FLAG_CARRYING = 12; +const int CTF_YELLOW_FLAG_TAKEN = 16; +const int CTF_YELLOW_FLAG_LOST = 32; +const int CTF_YELLOW_FLAG_CARRYING = 48; +const int CTF_PINK_FLAG_TAKEN = 64; +const int CTF_PINK_FLAG_LOST = 128; +const int CTF_PINK_FLAG_CARRYING = 192; +const int CTF_NEUTRAL_FLAG_TAKEN = 256; +const int CTF_NEUTRAL_FLAG_LOST = 512; +const int CTF_NEUTRAL_FLAG_CARRYING = 768; +const int CTF_FLAG_NEUTRAL = 2048; +const int CTF_SHIELDED = 4096; +const int CTF_STALEMATE = 8192; diff --git a/qcsrc/common/gamemodes/gamemode/cts/_mod.inc b/qcsrc/common/gamemodes/gamemode/cts/_mod.inc new file mode 100644 index 000000000..ab0d8a477 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/cts/_mod.inc @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/cts/_mod.qh b/qcsrc/common/gamemodes/gamemode/cts/_mod.qh new file mode 100644 index 000000000..a20b5c375 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/cts/_mod.qh @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/cts/cts.qc b/qcsrc/common/gamemodes/gamemode/cts/cts.qc new file mode 100644 index 000000000..12319c26c --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/cts/cts.qc @@ -0,0 +1,435 @@ +#include "cts.qh" + +// TODO: split into sv_cts +#ifdef SVQC +#include +#include + +float autocvar_g_cts_finish_kill_delay; +bool autocvar_g_cts_selfdamage; + +// legacy bot roles +.float race_checkpoint; +void havocbot_role_cts(entity this) +{ + if(IS_DEAD(this)) + return; + + if (navigation_goalrating_timeout(this)) + { + navigation_goalrating_start(this); + + bool raw_touch_check = true; + int cp = this.race_checkpoint; + + LABEL(search_racecheckpoints) + IL_EACH(g_racecheckpoints, true, + { + if(it.cnt == cp || cp == -1) + { + // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint + // e.g. checkpoint in front of Stormkeep's warpzone + // the same workaround is applied in Race game mode + if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30)) + { + cp = race_NextCheckpoint(cp); + raw_touch_check = false; + goto search_racecheckpoints; + } + navigation_routerating(this, it, 1000000, 5000); + } + }); + + navigation_goalrating_end(this); + + navigation_goalrating_timeout_set(this); + } +} + +void cts_ScoreRules() +{ + GameRules_score_enabled(false); + GameRules_scoring(0, 0, 0, { + if (g_race_qualifying) { + field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME); + } else { + field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY); + field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME); + field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME); + } + }); +} + +void cts_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later +{ + if(autocvar_sv_eventlog) + GameLogEcho(strcat(":cts:", mode, ":", ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : ""))); +} + +void KillIndicator_Think(entity this); +void CTS_ClientKill(entity e) // silent version of ClientKill, used when player finishes a CTS run. Useful to prevent cheating by running back to the start line and starting out with more speed +{ + e.killindicator = spawn(); + e.killindicator.owner = e; + setthink(e.killindicator, KillIndicator_Think); + e.killindicator.nextthink = time + (e.lip) * 0.05; + e.killindicator.cnt = ceil(autocvar_g_cts_finish_kill_delay); + e.killindicator.health = 1; // this is used to indicate that it should be silent + e.lip = 0; +} + +MUTATOR_HOOKFUNCTION(cts, PlayerPhysics) +{ + entity player = M_ARGV(0, entity); + float dt = M_ARGV(1, float); + + player.race_movetime_frac += dt; + float f = floor(player.race_movetime_frac); + player.race_movetime_frac -= f; + player.race_movetime_count += f; + player.race_movetime = player.race_movetime_frac + player.race_movetime_count; + +#ifdef SVQC + if(IS_PLAYER(player)) + { + if (player.race_penalty) + if (time > player.race_penalty) + player.race_penalty = 0; + if(player.race_penalty) + { + player.velocity = '0 0 0'; + set_movetype(player, MOVETYPE_NONE); + player.disableclientprediction = 2; + } + } +#endif + + // force kbd movement for fairness + float wishspeed; + vector wishvel; + + // if record times matter + // ensure nothing EVIL is being done (i.e. div0_evade) + // this hinders joystick users though + // but it still gives SOME analog control + wishvel.x = fabs(CS(player).movement.x); + wishvel.y = fabs(CS(player).movement.y); + if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y) + { + wishvel.z = 0; + wishspeed = vlen(wishvel); + if(wishvel.x >= 2 * wishvel.y) + { + // pure X motion + if(CS(player).movement.x > 0) + CS(player).movement_x = wishspeed; + else + CS(player).movement_x = -wishspeed; + CS(player).movement_y = 0; + } + else if(wishvel.y >= 2 * wishvel.x) + { + // pure Y motion + CS(player).movement_x = 0; + if(CS(player).movement.y > 0) + CS(player).movement_y = wishspeed; + else + CS(player).movement_y = -wishspeed; + } + else + { + // diagonal + if(CS(player).movement.x > 0) + CS(player).movement_x = M_SQRT1_2 * wishspeed; + else + CS(player).movement_x = -M_SQRT1_2 * wishspeed; + if(CS(player).movement.y > 0) + CS(player).movement_y = M_SQRT1_2 * wishspeed; + else + CS(player).movement_y = -M_SQRT1_2 * wishspeed; + } + } +} + +MUTATOR_HOOKFUNCTION(cts, reset_map_global) +{ + float s; + + Score_NicePrint(NULL); + + race_ClearRecords(); + PlayerScore_Sort(race_place, 0, 1, 0); + + FOREACH_CLIENT(true, { + if(it.race_place) + { + s = GameRules_scoring_add(it, RACE_FASTEST, 0); + if(!s) + it.race_place = 0; + } + cts_EventLog(ftos(it.race_place), it); + }); + + if(g_race_qualifying == 2) + { + g_race_qualifying = 0; + independent_players = 0; + cvar_set("fraglimit", ftos(race_fraglimit)); + cvar_set("leadlimit", ftos(race_leadlimit)); + cvar_set("timelimit", ftos(race_timelimit)); + cts_ScoreRules(); + } +} + +MUTATOR_HOOKFUNCTION(cts, ClientConnect) +{ + entity player = M_ARGV(0, entity); + + race_PreparePlayer(player); + player.race_checkpoint = -1; + + if(IS_REAL_CLIENT(player)) + { + string rr = CTS_RECORD; + + msg_entity = player; + race_send_recordtime(MSG_ONE); + race_send_speedaward(MSG_ONE); + + speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"))); + speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"))); + race_send_speedaward_alltimebest(MSG_ONE); + + float i; + int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt); + race_send_rankings_cnt(MSG_ONE); + for (i = 1; i <= m; ++i) + { + race_SendRankings(i, 0, 0, MSG_ONE); + } + } +} + +MUTATOR_HOOKFUNCTION(cts, AbortSpeedrun) +{ + entity player = M_ARGV(0, entity); + + if(autocvar_g_allow_checkpoints) + race_PreparePlayer(player); // nice try +} + +MUTATOR_HOOKFUNCTION(cts, MakePlayerObserver) +{ + entity player = M_ARGV(0, entity); + + if(GameRules_scoring_add(player, RACE_FASTEST, 0)) + player.frags = FRAGS_LMS_LOSER; + else + player.frags = FRAGS_SPECTATOR; + + race_PreparePlayer(player); + player.race_checkpoint = -1; +} + +MUTATOR_HOOKFUNCTION(cts, PlayerSpawn) +{ + entity player = M_ARGV(0, entity); + entity spawn_spot = M_ARGV(1, entity); + + if(spawn_spot.target == "") + // Emergency: this wasn't a real spawnpoint. Can this ever happen? + race_PreparePlayer(player); + + // if we need to respawn, do it right + player.race_respawn_checkpoint = player.race_checkpoint; + player.race_respawn_spotref = spawn_spot; + + player.race_place = 0; +} + +MUTATOR_HOOKFUNCTION(cts, PutClientInServer) +{ + entity player = M_ARGV(0, entity); + + if(IS_PLAYER(player)) + if(!game_stopped) + { + if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn + race_PreparePlayer(player); + else // respawn + race_RetractPlayer(player); + + race_AbandonRaceCheck(player); + } +} + +MUTATOR_HOOKFUNCTION(cts, PlayerDies) +{ + entity frag_target = M_ARGV(2, entity); + + frag_target.respawn_flags |= RESPAWN_FORCE; + race_AbandonRaceCheck(frag_target); +} + +MUTATOR_HOOKFUNCTION(cts, HavocBot_ChooseRole) +{ + entity bot = M_ARGV(0, entity); + + bot.havocbot_role = havocbot_role_cts; + return true; +} + +MUTATOR_HOOKFUNCTION(cts, GetPressedKeys) +{ + entity player = M_ARGV(0, entity); + + if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1) + { + if (!player.stored_netname) + player.stored_netname = strzone(uid2name(player.crypto_idfp)); + if(player.stored_netname != player.netname) + { + db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname); + strcpy(player.stored_netname, player.netname); + } + } + + if (!IS_OBSERVER(player)) + { + if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed)) + { + speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1'); + speedaward_holder = player.netname; + speedaward_uid = player.crypto_idfp; + speedaward_lastupdate = time; + } + if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1) + { + string rr = CTS_RECORD; + race_send_speedaward(MSG_ALL); + speedaward_lastsent = speedaward_speed; + if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "") + { + speedaward_alltimebest = speedaward_speed; + speedaward_alltimebest_holder = speedaward_holder; + speedaward_alltimebest_uid = speedaward_uid; + db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest)); + db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid); + race_send_speedaward_alltimebest(MSG_ALL); + } + } + } +} + +MUTATOR_HOOKFUNCTION(cts, ForbidThrowCurrentWeapon) +{ + // no weapon dropping in CTS + return true; +} + +MUTATOR_HOOKFUNCTION(cts, FilterItem) +{ + entity item = M_ARGV(0, entity); + + if (Item_IsLoot(item)) + { + return true; + } +} + +MUTATOR_HOOKFUNCTION(cts, Damage_Calculate) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + float frag_deathtype = M_ARGV(3, float); + float frag_damage = M_ARGV(4, float); + + if(frag_target == frag_attacker || frag_deathtype == DEATH_FALL.m_id) + if(!autocvar_g_cts_selfdamage) + { + frag_damage = 0; + M_ARGV(4, float) = frag_damage; + } +} + +MUTATOR_HOOKFUNCTION(cts, ForbidPlayerScore_Clear) +{ + return true; // in CTS, you don't lose score by observing +} + +MUTATOR_HOOKFUNCTION(cts, GetRecords) +{ + int record_page = M_ARGV(0, int); + string ret_string = M_ARGV(1, string); + + for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i) + { + if(MapInfo_Get_ByID(i)) + { + float r = race_readTime(MapInfo_Map_bspname, 1); + + if(!r) + continue; + + string h = race_readName(MapInfo_Map_bspname, 1); + ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n"); + } + } + + M_ARGV(1, string) = ret_string; +} + +void ClientKill_Now(entity this); +MUTATOR_HOOKFUNCTION(cts, ClientKill) +{ + entity player = M_ARGV(0, entity); + + M_ARGV(1, float) = 0; // kill delay + + if(player.killindicator && player.killindicator.health == 1) // player.killindicator.health == 1 means that the kill indicator was spawned by CTS_ClientKill + { + delete(player.killindicator); + player.killindicator = NULL; + + ClientKill_Now(player); // allow instant kill in this case + return; + } +} + +MUTATOR_HOOKFUNCTION(cts, Race_FinalCheckpoint) +{ + entity player = M_ARGV(0, entity); + + if(autocvar_g_cts_finish_kill_delay) + CTS_ClientKill(player); +} + +MUTATOR_HOOKFUNCTION(cts, HideTeamNagger) +{ + return true; // doesn't work so well (but isn't cts a teamless mode?) +} + +MUTATOR_HOOKFUNCTION(cts, FixClientCvars) +{ + entity player = M_ARGV(0, entity); + + stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n"); +} + +MUTATOR_HOOKFUNCTION(cts, WantWeapon) +{ + M_ARGV(1, float) = (M_ARGV(0, entity) == WEP_SHOTGUN); // want weapon = weapon info + M_ARGV(3, bool) = true; // want mutator blocked + return true; +} + +MUTATOR_HOOKFUNCTION(cts, ForbidDropCurrentWeapon) +{ + return true; +} + +void cts_Initialize() +{ + cts_ScoreRules(); +} +#endif diff --git a/qcsrc/common/gamemodes/gamemode/cts/cts.qh b/qcsrc/common/gamemodes/gamemode/cts/cts.qh new file mode 100644 index 000000000..516e903a9 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/cts/cts.qh @@ -0,0 +1,26 @@ +#pragma once + +#ifdef SVQC +#include +#include + +void cts_Initialize(); + +REGISTER_MUTATOR(cts, false) +{ + MUTATOR_STATIC(); + MUTATOR_ONADD + { + g_race_qualifying = true; + independent_players = 1; + GameRules_limit_score(0); + GameRules_limit_lead(0); + + cts_Initialize(); + } + return 0; +} + +// scores +const float ST_CTS_LAPS = 1; +#endif diff --git a/qcsrc/common/gamemodes/gamemode/deathmatch/_mod.inc b/qcsrc/common/gamemodes/gamemode/deathmatch/_mod.inc new file mode 100644 index 000000000..2403aad75 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/deathmatch/_mod.inc @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/deathmatch/_mod.qh b/qcsrc/common/gamemodes/gamemode/deathmatch/_mod.qh new file mode 100644 index 000000000..2135ec9d8 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/deathmatch/_mod.qh @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/deathmatch/deathmatch.qc b/qcsrc/common/gamemodes/gamemode/deathmatch/deathmatch.qc new file mode 100644 index 000000000..5cd7ca1f5 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/deathmatch/deathmatch.qc @@ -0,0 +1,10 @@ +#include "deathmatch.qh" + +// TODO: sv_deathmatch? +#ifdef SVQC +MUTATOR_HOOKFUNCTION(dm, Scores_CountFragsRemaining) +{ + // announce remaining frags + return true; +} +#endif diff --git a/qcsrc/common/gamemodes/gamemode/deathmatch/deathmatch.qh b/qcsrc/common/gamemodes/gamemode/deathmatch/deathmatch.qh new file mode 100644 index 000000000..fdae27863 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/deathmatch/deathmatch.qh @@ -0,0 +1,10 @@ +#pragma once + +#ifdef SVQC +#include +REGISTER_MUTATOR(dm, false) +{ + MUTATOR_STATIC(); + return 0; +} +#endif diff --git a/qcsrc/common/gamemodes/gamemode/domination/_mod.inc b/qcsrc/common/gamemodes/gamemode/domination/_mod.inc new file mode 100644 index 000000000..95d00b389 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/domination/_mod.inc @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/domination/_mod.qh b/qcsrc/common/gamemodes/gamemode/domination/_mod.qh new file mode 100644 index 000000000..e57c30efe --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/domination/_mod.qh @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/domination/domination.qc b/qcsrc/common/gamemodes/gamemode/domination/domination.qc new file mode 100644 index 000000000..3faa4a89a --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/domination/domination.qc @@ -0,0 +1,673 @@ +#include "domination.qh" + +// TODO: sv_domination +#ifdef SVQC +#include + +bool g_domination; + +int autocvar_g_domination_default_teams; +bool autocvar_g_domination_disable_frags; +int autocvar_g_domination_point_amt; +bool autocvar_g_domination_point_fullbright; +float autocvar_g_domination_round_timelimit; +float autocvar_g_domination_warmup; +float autocvar_g_domination_point_rate; +int autocvar_g_domination_teams_override; + +void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later +{ + if(autocvar_sv_eventlog) + GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : ""))); +} + +void set_dom_state(entity e) +{ + STAT(DOM_TOTAL_PPS, e) = total_pps; + STAT(DOM_PPS_RED, e) = pps_red; + STAT(DOM_PPS_BLUE, e) = pps_blue; + if(domination_teams >= 3) + STAT(DOM_PPS_YELLOW, e) = pps_yellow; + if(domination_teams >= 4) + STAT(DOM_PPS_PINK, e) = pps_pink; +} + +void dompoint_captured(entity this) +{ + float old_delay, old_team, real_team; + + // now that the delay has expired, switch to the latest team to lay claim to this point + entity head = this.owner; + + real_team = this.cnt; + this.cnt = -1; + + dom_EventLog("taken", this.team, this.dmg_inflictor); + this.dmg_inflictor = NULL; + + this.goalentity = head; + this.model = head.mdl; + this.modelindex = head.dmg; + this.skin = head.skin; + + float points, wait_time; + if (autocvar_g_domination_point_amt) + points = autocvar_g_domination_point_amt; + else + points = this.frags; + if (autocvar_g_domination_point_rate) + wait_time = autocvar_g_domination_point_rate; + else + wait_time = this.wait; + + if(domination_roundbased) + bprint(sprintf("^3%s^3%s\n", head.netname, this.message)); + else + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time); + + if(this.enemy.playerid == this.enemy_playerid) + GameRules_scoring_add(this.enemy, DOM_TAKES, 1); + else + this.enemy = NULL; + + if (head.noise != "") + if(this.enemy) + _sound(this.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM); + else + _sound(this, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM); + if (head.noise1 != "") + play2all(head.noise1); + + this.delay = time + wait_time; + + // do trigger work + old_delay = this.delay; + old_team = this.team; + this.team = real_team; + this.delay = 0; + SUB_UseTargets (this, this, NULL); + this.delay = old_delay; + this.team = old_team; + + entity msg = WP_DomNeut; + switch(real_team) + { + case NUM_TEAM_1: msg = WP_DomRed; break; + case NUM_TEAM_2: msg = WP_DomBlue; break; + case NUM_TEAM_3: msg = WP_DomYellow; break; + case NUM_TEAM_4: msg = WP_DomPink; break; + } + + WaypointSprite_UpdateSprites(this.sprite, msg, WP_Null, WP_Null); + + total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0; + IL_EACH(g_dompoints, true, + { + if (autocvar_g_domination_point_amt) + points = autocvar_g_domination_point_amt; + else + points = it.frags; + if (autocvar_g_domination_point_rate) + wait_time = autocvar_g_domination_point_rate; + else + wait_time = it.wait; + switch(it.goalentity.team) + { + case NUM_TEAM_1: pps_red += points/wait_time; break; + case NUM_TEAM_2: pps_blue += points/wait_time; break; + case NUM_TEAM_3: pps_yellow += points/wait_time; break; + case NUM_TEAM_4: pps_pink += points/wait_time; break; + } + total_pps += points/wait_time; + }); + + WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0)); + WaypointSprite_Ping(this.sprite); + + this.captime = time; + + FOREACH_CLIENT(IS_REAL_CLIENT(it), { set_dom_state(it); }); +} + +void AnimateDomPoint(entity this) +{ + if(this.pain_finished > time) + return; + this.pain_finished = time + this.t_width; + if(this.nextthink > this.pain_finished) + this.nextthink = this.pain_finished; + + this.frame = this.frame + 1; + if(this.frame > this.t_length) + this.frame = 0; +} + +void dompointthink(entity this) +{ + float fragamt; + + this.nextthink = time + 0.1; + + //this.frame = this.frame + 1; + //if(this.frame > 119) + // this.frame = 0; + AnimateDomPoint(this); + + // give points + + if (game_stopped || this.delay > time || time < game_starttime) // game has ended, don't keep giving points + return; + + if(autocvar_g_domination_point_rate) + this.delay = time + autocvar_g_domination_point_rate; + else + this.delay = time + this.wait; + + // give credit to the team + // NOTE: this defaults to 0 + if (!domination_roundbased) + if (this.goalentity.netname != "") + { + if(autocvar_g_domination_point_amt) + fragamt = autocvar_g_domination_point_amt; + else + fragamt = this.frags; + TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt); + TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt); + + // give credit to the individual player, if he is still there + if (this.enemy.playerid == this.enemy_playerid) + { + GameRules_scoring_add(this.enemy, SCORE, fragamt); + GameRules_scoring_add(this.enemy, DOM_TICKS, fragamt); + } + else + this.enemy = NULL; + } +} + +void dompointtouch(entity this, entity toucher) +{ + if (!IS_PLAYER(toucher)) + return; + if (toucher.health < 1) + return; + + if(round_handler_IsActive() && !round_handler_IsRoundStarted()) + return; + + if(time < this.captime + 0.3) + return; + + // only valid teams can claim it + entity head = find(NULL, classname, "dom_team"); + while (head && head.team != toucher.team) + head = find(head, classname, "dom_team"); + if (!head || head.netname == "" || head == this.goalentity) + return; + + // delay capture + + this.team = this.goalentity.team; // this stores the PREVIOUS team! + + this.cnt = toucher.team; + this.owner = head; // team to switch to after the delay + this.dmg_inflictor = toucher; + + // this.state = 1; + // this.delay = time + cvar("g_domination_point_capturetime"); + //this.nextthink = time + cvar("g_domination_point_capturetime"); + //this.think = dompoint_captured; + + // go to neutral team in the mean time + head = find(NULL, classname, "dom_team"); + while (head && head.netname != "") + head = find(head, classname, "dom_team"); + if(head == NULL) + return; + + WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null); + WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1'); + WaypointSprite_Ping(this.sprite); + + this.goalentity = head; + this.model = head.mdl; + this.modelindex = head.dmg; + this.skin = head.skin; + + this.enemy = toucher; // individual player scoring + this.enemy_playerid = toucher.playerid; + dompoint_captured(this); +} + +void dom_controlpoint_setup(entity this) +{ + entity head; + // find the spawnfunc_dom_team representing unclaimed points + head = find(NULL, classname, "dom_team"); + while(head && head.netname != "") + head = find(head, classname, "dom_team"); + if (!head) + objerror(this, "no spawnfunc_dom_team with netname \"\" found\n"); + + // copy important properties from spawnfunc_dom_team entity + this.goalentity = head; + _setmodel(this, head.mdl); // precision already set + this.skin = head.skin; + + this.cnt = -1; + + if(this.message == "") + this.message = " has captured a control point"; + + if(this.frags <= 0) + this.frags = 1; + if(this.wait <= 0) + this.wait = 5; + + float points, waittime; + if (autocvar_g_domination_point_amt) + points = autocvar_g_domination_point_amt; + else + points = this.frags; + if (autocvar_g_domination_point_rate) + waittime = autocvar_g_domination_point_rate; + else + waittime = this.wait; + + total_pps += points/waittime; + + if(!this.t_width) + this.t_width = 0.02; // frame animation rate + if(!this.t_length) + this.t_length = 239; // maximum frame + + setthink(this, dompointthink); + this.nextthink = time; + settouch(this, dompointtouch); + this.solid = SOLID_TRIGGER; + if(!this.flags & FL_ITEM) + IL_PUSH(g_items, this); + this.flags = FL_ITEM; + setsize(this, '-32 -32 -32', '32 32 32'); + setorigin(this, this.origin + '0 0 20'); + droptofloor(this); + + waypoint_spawnforitem(this); + WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT); +} + +float total_controlpoints; +void Domination_count_controlpoints() +{ + total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0; + IL_EACH(g_dompoints, true, + { + ++total_controlpoints; + redowned += (it.goalentity.team == NUM_TEAM_1); + blueowned += (it.goalentity.team == NUM_TEAM_2); + yellowowned += (it.goalentity.team == NUM_TEAM_3); + pinkowned += (it.goalentity.team == NUM_TEAM_4); + }); +} + +float Domination_GetWinnerTeam() +{ + float winner_team = 0; + if(redowned == total_controlpoints) + winner_team = NUM_TEAM_1; + if(blueowned == total_controlpoints) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_2; + } + if(yellowowned == total_controlpoints) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_3; + } + if(pinkowned == total_controlpoints) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_4; + } + if(winner_team) + return winner_team; + return -1; // no control points left? +} + +#define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0)) +#define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints) +float Domination_CheckWinner() +{ + if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER); + + game_stopped = true; + round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit); + return 1; + } + + Domination_count_controlpoints(); + + float winner_team = Domination_GetWinnerTeam(); + + if(winner_team == -1) + return 0; + + if(winner_team > 0) + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN)); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN)); + TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1); + } + else if(winner_team == -1) + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED); + } + + game_stopped = true; + round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit); + + return 1; +} + +float Domination_CheckPlayers() +{ + return 1; +} + +void Domination_RoundStart() +{ + FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; }); +} + +//go to best items, or control points you don't own +void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius) +{ + IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius), + { + if(it.cnt > -1) // this is just being fought + navigation_routerating(this, it, ratingscale, 5000); + else if(it.goalentity.cnt == 0) // unclaimed + navigation_routerating(this, it, ratingscale * 0.5, 5000); + else if(it.goalentity.team != this.team) // other team's point + navigation_routerating(this, it, ratingscale * 0.2, 5000); + }); +} + +void havocbot_role_dom(entity this) +{ + if(IS_DEAD(this)) + return; + + if (navigation_goalrating_timeout(this)) + { + navigation_goalrating_start(this); + havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000); + havocbot_goalrating_items(this, 8000, this.origin, 8000); + //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000); + havocbot_goalrating_waypoints(this, 1, this.origin, 3000); + navigation_goalrating_end(this); + + navigation_goalrating_timeout_set(this); + } +} + +MUTATOR_HOOKFUNCTION(dom, CheckAllowedTeams) +{ + // fallback? + M_ARGV(0, float) = domination_teams; + string ret_string = "dom_team"; + + entity head = find(NULL, classname, ret_string); + while(head) + { + if(head.netname != "") + { + switch(head.team) + { + case NUM_TEAM_1: c1 = 0; break; + case NUM_TEAM_2: c2 = 0; break; + case NUM_TEAM_3: c3 = 0; break; + case NUM_TEAM_4: c4 = 0; break; + } + } + + head = find(head, classname, ret_string); + } + + M_ARGV(1, string) = string_null; + + return true; +} + +MUTATOR_HOOKFUNCTION(dom, reset_map_players) +{ + total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0; + FOREACH_CLIENT(IS_PLAYER(it), { + PutClientInServer(it); + if(domination_roundbased) + it.player_blocked = 1; + if(IS_REAL_CLIENT(it)) + set_dom_state(it); + }); + return true; +} + +MUTATOR_HOOKFUNCTION(dom, PlayerSpawn) +{ + entity player = M_ARGV(0, entity); + + if(domination_roundbased) + if(!round_handler_IsRoundStarted()) + player.player_blocked = 1; + else + player.player_blocked = 0; +} + +MUTATOR_HOOKFUNCTION(dom, ClientConnect) +{ + entity player = M_ARGV(0, entity); + + set_dom_state(player); +} + +MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole) +{ + entity bot = M_ARGV(0, entity); + + bot.havocbot_role = havocbot_role_dom; + return true; +} + +/*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32) +Control point for Domination gameplay. +*/ +spawnfunc(dom_controlpoint) +{ + if(!g_domination) + { + delete(this); + return; + } + setthink(this, dom_controlpoint_setup); + this.nextthink = time + 0.1; + this.reset = dom_controlpoint_setup; + + if(!this.scale) + this.scale = 0.6; + + this.effects = this.effects | EF_LOWPRECISION; + if (autocvar_g_domination_point_fullbright) + this.effects |= EF_FULLBRIGHT; + + IL_PUSH(g_dompoints, this); +} + +/*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32) +Team declaration for Domination gameplay, this allows you to decide what team +names and control point models are used in your map. + +Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two +can have netname set! The nameless team owns all control points at start. + +Keys: +"netname" + Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc) +"cnt" + Scoreboard color of the team (for example 4 is red and 13 is blue) +"model" + Model to use for control points owned by this team (for example + "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver + keycard) +"skin" + Skin of the model to use (for team skins on a single model) +"noise" + Sound to play when this team captures a point. + (this is a localized sound, like a small alarm or other effect) +"noise1" + Narrator speech to play when this team captures a point. + (this is a global sound, like "Red team has captured a control point") +*/ + +spawnfunc(dom_team) +{ + if(!g_domination || autocvar_g_domination_teams_override >= 2) + { + delete(this); + return; + } + precache_model(this.model); + if (this.noise != "") + precache_sound(this.noise); + if (this.noise1 != "") + precache_sound(this.noise1); + this.classname = "dom_team"; + _setmodel(this, this.model); // precision not needed + this.mdl = this.model; + this.dmg = this.modelindex; + this.model = ""; + this.modelindex = 0; + // this would have to be changed if used in quakeworld + if(this.cnt) + this.team = this.cnt + 1; // WHY are these different anyway? +} + +// scoreboard setup +void ScoreRules_dom(int teams) +{ + if(domination_roundbased) + { + GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, { + field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY); + field(SP_DOM_TAKES, "takes", 0); + }); + } + else + { + float sp_domticks, sp_score; + sp_score = sp_domticks = 0; + if(autocvar_g_domination_disable_frags) + sp_domticks = SFL_SORT_PRIO_PRIMARY; + else + sp_score = SFL_SORT_PRIO_PRIMARY; + GameRules_scoring(teams, sp_score, sp_score, { + field_team(ST_DOM_TICKS, "ticks", sp_domticks); + field(SP_DOM_TICKS, "ticks", sp_domticks); + field(SP_DOM_TAKES, "takes", 0); + }); + } +} + +// code from here on is just to support maps that don't have control point and team entities +void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage) +{ + TC(Sound, capsound); + entity e = new_pure(dom_team); + e.netname = strzone(teamname); + e.cnt = teamcolor; + e.model = pointmodel; + e.skin = pointskin; + e.noise = strzone(Sound_fixpath(capsound)); + e.noise1 = strzone(capnarration); + e.message = strzone(capmessage); + + // this code is identical to spawnfunc_dom_team + _setmodel(e, e.model); // precision not needed + e.mdl = e.model; + e.dmg = e.modelindex; + e.model = ""; + e.modelindex = 0; + // this would have to be changed if used in quakeworld + e.team = e.cnt + 1; + + //eprint(e); +} + +void dom_spawnpoint(vector org) +{ + entity e = spawn(); + e.classname = "dom_controlpoint"; + setthink(e, spawnfunc_dom_controlpoint); + e.nextthink = time; + setorigin(e, org); + spawnfunc_dom_controlpoint(e); +} + +// spawn some default teams if the map is not set up for domination +void dom_spawnteams(int teams) +{ + TC(int, teams); + dom_spawnteam(Team_ColoredFullName(NUM_TEAM_1), NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND_DOM_CLAIM, "", "Red team has captured a control point"); + dom_spawnteam(Team_ColoredFullName(NUM_TEAM_2), NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND_DOM_CLAIM, "", "Blue team has captured a control point"); + if(teams >= 3) + dom_spawnteam(Team_ColoredFullName(NUM_TEAM_3), NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, SND_DOM_CLAIM, "", "Yellow team has captured a control point"); + if(teams >= 4) + dom_spawnteam(Team_ColoredFullName(NUM_TEAM_4), NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, SND_DOM_CLAIM, "", "Pink team has captured a control point"); + dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", ""); +} + +void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up. +{ + // if no teams are found, spawn defaults + if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2) + { + LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway."); + domination_teams = autocvar_g_domination_teams_override; + if (domination_teams < 2) + domination_teams = autocvar_g_domination_default_teams; + domination_teams = bound(2, domination_teams, 4); + dom_spawnteams(domination_teams); + } + + CheckAllowedTeams(NULL); + //domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2); + + int teams = 0; + if(c1 >= 0) teams |= BIT(0); + if(c2 >= 0) teams |= BIT(1); + if(c3 >= 0) teams |= BIT(2); + if(c4 >= 0) teams |= BIT(3); + domination_teams = teams; + + domination_roundbased = autocvar_g_domination_roundbased; + + ScoreRules_dom(domination_teams); + + if(domination_roundbased) + { + round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart); + round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit); + } +} + +void dom_Initialize() +{ + g_domination = true; + InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE); +} +#endif diff --git a/qcsrc/common/gamemodes/gamemode/domination/domination.qh b/qcsrc/common/gamemodes/gamemode/domination/domination.qh new file mode 100644 index 000000000..f4faf50a4 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/domination/domination.qh @@ -0,0 +1,54 @@ +#pragma once + +#ifdef SVQC +#include +#define autocvar_g_domination_point_limit cvar("g_domination_point_limit") +bool autocvar_g_domination_roundbased; +int autocvar_g_domination_roundbased_point_limit; +int autocvar_g_domination_point_leadlimit; + +void dom_Initialize(); + +REGISTER_MUTATOR(dom, false) +{ + MUTATOR_STATIC(); + MUTATOR_ONADD + { + int fraglimit_override = autocvar_g_domination_point_limit; + if (autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit) + fraglimit_override = autocvar_g_domination_roundbased_point_limit; + + GameRules_teams(true); + GameRules_limit_score(fraglimit_override); + GameRules_limit_lead(autocvar_g_domination_point_leadlimit); + + dom_Initialize(); + } + return 0; +} + +// score rule declarations +const float ST_DOM_TICKS = 1; +const float ST_DOM_CAPS = 1; + +// pps: points per second +float total_pps; +float pps_red; +float pps_blue; +float pps_yellow; +float pps_pink; + +// capture declarations +.float enemy_playerid; +.entity sprite; +.float captime; + +// misc globals +float domination_roundbased; +float domination_teams; + +void AnimateDomPoint(entity this); + +IntrusiveList g_dompoints; +STATIC_INIT(g_dompoints) { g_dompoints = IL_NEW(); } +#endif diff --git a/qcsrc/common/gamemodes/gamemode/freezetag/_mod.inc b/qcsrc/common/gamemodes/gamemode/freezetag/_mod.inc new file mode 100644 index 000000000..aff5bf9d7 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/freezetag/_mod.inc @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/freezetag/_mod.qh b/qcsrc/common/gamemodes/gamemode/freezetag/_mod.qh new file mode 100644 index 000000000..1bc21821a --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/freezetag/_mod.qh @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/freezetag/freezetag.qc b/qcsrc/common/gamemodes/gamemode/freezetag/freezetag.qc new file mode 100644 index 000000000..cceff4806 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/freezetag/freezetag.qc @@ -0,0 +1,588 @@ +#include "freezetag.qh" + +// TODO: sv_freezetag +#ifdef SVQC +float autocvar_g_freezetag_frozen_maxtime; +float autocvar_g_freezetag_revive_clearspeed; +float autocvar_g_freezetag_round_timelimit; +//int autocvar_g_freezetag_teams; +int autocvar_g_freezetag_teams_override; +float autocvar_g_freezetag_warmup; + +void freezetag_count_alive_players() +{ + total_players = redalive = bluealive = yellowalive = pinkalive = 0; + FOREACH_CLIENT(IS_PLAYER(it), { + switch(it.team) + { + case NUM_TEAM_1: ++total_players; if(it.health >= 1 && STAT(FROZEN, it) != 1) ++redalive; break; + case NUM_TEAM_2: ++total_players; if(it.health >= 1 && STAT(FROZEN, it) != 1) ++bluealive; break; + case NUM_TEAM_3: ++total_players; if(it.health >= 1 && STAT(FROZEN, it) != 1) ++yellowalive; break; + case NUM_TEAM_4: ++total_players; if(it.health >= 1 && STAT(FROZEN, it) != 1) ++pinkalive; break; + } + }); + FOREACH_CLIENT(IS_REAL_CLIENT(it), { + STAT(REDALIVE, it) = redalive; + STAT(BLUEALIVE, it) = bluealive; + STAT(YELLOWALIVE, it) = yellowalive; + STAT(PINKALIVE, it) = pinkalive; + }); + + eliminatedPlayers.SendFlags |= 1; +} +#define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0)) +#define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == NumTeams(freezetag_teams)) + +float freezetag_CheckTeams() +{ + static float prev_missing_teams_mask; + if(FREEZETAG_ALIVE_TEAMS_OK()) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + return 1; + } + if(total_players == 0) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + return 0; + } + int missing_teams_mask = 0; + if(freezetag_teams & BIT(0)) + missing_teams_mask += (!redalive) * 1; + if(freezetag_teams & BIT(1)) + missing_teams_mask += (!bluealive) * 2; + if(freezetag_teams & BIT(2)) + missing_teams_mask += (!yellowalive) * 4; + if(freezetag_teams & BIT(3)) + missing_teams_mask += (!pinkalive) * 8; + if(prev_missing_teams_mask != missing_teams_mask) + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask); + prev_missing_teams_mask = missing_teams_mask; + } + return 0; +} + +float freezetag_getWinnerTeam() +{ + float winner_team = 0; + if(redalive >= 1) + winner_team = NUM_TEAM_1; + if(bluealive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_2; + } + if(yellowalive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_3; + } + if(pinkalive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_4; + } + if(winner_team) + return winner_team; + return -1; // no player left +} + +void nades_Clear(entity); +void nades_GiveBonus(entity player, float score); + +float freezetag_CheckWinner() +{ + if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER); + FOREACH_CLIENT(IS_PLAYER(it), { + it.freezetag_frozen_timeout = 0; + nades_Clear(it); + }); + game_stopped = true; + round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit); + return 1; + } + + if(FREEZETAG_ALIVE_TEAMS() > 1) + return 0; + + int winner_team = freezetag_getWinnerTeam(); + if(winner_team > 0) + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN)); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN)); + TeamScore_AddToTeam(winner_team, ST_SCORE, +1); + } + else if(winner_team == -1) + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED); + } + + FOREACH_CLIENT(IS_PLAYER(it), { + it.freezetag_frozen_timeout = 0; + nades_Clear(it); + }); + + game_stopped = true; + round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit); + return 1; +} + +entity freezetag_LastPlayerForTeam(entity this) +{ + entity last_pl = NULL; + FOREACH_CLIENT(IS_PLAYER(it) && it != this, { + if(it.health >= 1) + if(!STAT(FROZEN, it)) + if(SAME_TEAM(it, this)) + if(!last_pl) + last_pl = it; + else + return NULL; + }); + return last_pl; +} + +void freezetag_LastPlayerForTeam_Notify(entity this) +{ + if(round_handler_IsActive()) + if(round_handler_IsRoundStarted()) + { + entity pl = freezetag_LastPlayerForTeam(this); + if(pl) + Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE); + } +} + +void freezetag_Add_Score(entity targ, entity attacker) +{ + if(attacker == targ) + { + // you froze your own dumb targ + // counted as "suicide" already + GameRules_scoring_add(targ, SCORE, -1); + } + else if(IS_PLAYER(attacker)) + { + // got frozen by an enemy + // counted as "kill" and "death" already + GameRules_scoring_add(targ, SCORE, -1); + GameRules_scoring_add(attacker, SCORE, +1); + } + // else nothing - got frozen by the game type rules themselves +} + +void freezetag_Freeze(entity targ, entity attacker) +{ + if(STAT(FROZEN, targ)) + return; + + if(autocvar_g_freezetag_frozen_maxtime > 0) + targ.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime; + + Freeze(targ, 0, 1, true); + + freezetag_count_alive_players(); + + freezetag_Add_Score(targ, attacker); +} + +void freezetag_Unfreeze(entity this) +{ + this.freezetag_frozen_time = 0; + this.freezetag_frozen_timeout = 0; + + Unfreeze(this); +} + +float freezetag_isEliminated(entity e) +{ + if(IS_PLAYER(e) && (STAT(FROZEN, e) == 1 || IS_DEAD(e))) + return true; + return false; +} + + +// ================ +// Bot player logic +// ================ + +void(entity this) havocbot_role_ft_freeing; +void(entity this) havocbot_role_ft_offense; + +void havocbot_goalrating_freeplayers(entity this, float ratingscale, vector org, float sradius) +{ + float t; + FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), { + if (STAT(FROZEN, it) == 1) + { + if(vdist(it.origin - org, >, sradius)) + continue; + navigation_routerating(this, it, ratingscale, 2000); + } + else if(vdist(it.origin - org, >, 400)) // avoid gathering all teammates in one place + { + // If teamate is not frozen still seek them out as fight better + // in a group. + t = 0.2 * 150 / (this.health + this.armorvalue); + navigation_routerating(this, it, t * ratingscale, 2000); + } + }); +} + +void havocbot_role_ft_offense(entity this) +{ + if(IS_DEAD(this)) + return; + + if (!this.havocbot_role_timeout) + this.havocbot_role_timeout = time + random() * 10 + 20; + + // Count how many players on team are unfrozen. + int unfrozen = 0; + FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this) && !(STAT(FROZEN, it) != 1), { unfrozen++; }); + + // If only one left on team or if role has timed out then start trying to free players. + if (((unfrozen == 0) && (!STAT(FROZEN, this))) || (time > this.havocbot_role_timeout)) + { + LOG_TRACE("changing role to freeing"); + this.havocbot_role = havocbot_role_ft_freeing; + this.havocbot_role_timeout = 0; + return; + } + + if (navigation_goalrating_timeout(this)) + { + navigation_goalrating_start(this); + havocbot_goalrating_items(this, 10000, this.origin, 10000); + havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000); + havocbot_goalrating_freeplayers(this, 9000, this.origin, 10000); + havocbot_goalrating_waypoints(this, 1, this.origin, 3000); + navigation_goalrating_end(this); + + navigation_goalrating_timeout_set(this); + } +} + +void havocbot_role_ft_freeing(entity this) +{ + if(IS_DEAD(this)) + return; + + if (!this.havocbot_role_timeout) + this.havocbot_role_timeout = time + random() * 10 + 20; + + if (time > this.havocbot_role_timeout) + { + LOG_TRACE("changing role to offense"); + this.havocbot_role = havocbot_role_ft_offense; + this.havocbot_role_timeout = 0; + return; + } + + if (navigation_goalrating_timeout(this)) + { + navigation_goalrating_start(this); + havocbot_goalrating_items(this, 8000, this.origin, 10000); + havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000); + havocbot_goalrating_freeplayers(this, 20000, this.origin, 10000); + havocbot_goalrating_waypoints(this, 1, this.origin, 3000); + navigation_goalrating_end(this); + + navigation_goalrating_timeout_set(this); + } +} + + +// ============== +// Hook Functions +// ============== + +void ft_RemovePlayer(entity this) +{ + this.health = 0; // neccessary to update correctly alive stats + if(!STAT(FROZEN, this)) + freezetag_LastPlayerForTeam_Notify(this); + freezetag_Unfreeze(this); + freezetag_count_alive_players(); +} + +MUTATOR_HOOKFUNCTION(ft, ClientDisconnect) +{ + entity player = M_ARGV(0, entity); + + ft_RemovePlayer(player); + return true; +} + +MUTATOR_HOOKFUNCTION(ft, MakePlayerObserver) +{ + entity player = M_ARGV(0, entity); + + ft_RemovePlayer(player); +} + +MUTATOR_HOOKFUNCTION(ft, PlayerDies) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + float frag_deathtype = M_ARGV(3, float); + + if(round_handler_IsActive()) + if(round_handler_CountdownRunning()) + { + if(STAT(FROZEN, frag_target)) + freezetag_Unfreeze(frag_target); + freezetag_count_alive_players(); + return true; // let the player die so that he can respawn whenever he wants + } + + // Cases DEATH_TEAMCHANGE and DEATH_AUTOTEAMCHANGE are needed to fix a bug whe + // you succeed changing team through the menu: you both really die (gibbing) and get frozen + if(ITEM_DAMAGE_NEEDKILL(frag_deathtype) + || frag_deathtype == DEATH_TEAMCHANGE.m_id || frag_deathtype == DEATH_AUTOTEAMCHANGE.m_id) + { + // let the player die, he will be automatically frozen when he respawns + if(STAT(FROZEN, frag_target) != 1) + { + freezetag_Add_Score(frag_target, frag_attacker); + freezetag_count_alive_players(); + freezetag_LastPlayerForTeam_Notify(frag_target); + } + else + freezetag_Unfreeze(frag_target); // remove ice + frag_target.health = 0; // Unfreeze resets health + frag_target.freezetag_frozen_timeout = -2; // freeze on respawn + return true; + } + + if(STAT(FROZEN, frag_target)) + return true; + + freezetag_Freeze(frag_target, frag_attacker); + freezetag_LastPlayerForTeam_Notify(frag_target); + + if(frag_attacker == frag_target || frag_attacker == NULL) + { + if(IS_PLAYER(frag_target)) + Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname); + } + else + { + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname); + } + + return true; +} + +MUTATOR_HOOKFUNCTION(ft, PlayerSpawn) +{ + entity player = M_ARGV(0, entity); + + if(player.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players + return true; // do nothing, round is starting right now + + if(player.freezetag_frozen_timeout == -2) // player was dead + { + freezetag_Freeze(player, NULL); + return true; + } + + freezetag_count_alive_players(); + + if(round_handler_IsActive()) + if(round_handler_IsRoundStarted()) + { + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_SPAWN_LATE); + freezetag_Freeze(player, NULL); + } + + return true; +} + +MUTATOR_HOOKFUNCTION(ft, reset_map_players) +{ + FOREACH_CLIENT(IS_PLAYER(it), { + CS(it).killcount = 0; + it.freezetag_frozen_timeout = -1; + PutClientInServer(it); + it.freezetag_frozen_timeout = 0; + }); + freezetag_count_alive_players(); + return true; +} + +MUTATOR_HOOKFUNCTION(ft, GiveFragsForKill, CBC_ORDER_FIRST) +{ + M_ARGV(2, float) = 0; // no frags counted in Freeze Tag + return true; +} + +MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST) +{ + if(game_stopped) + return true; + + if(round_handler_IsActive()) + if(!round_handler_IsRoundStarted()) + return true; + + int n; + entity o = NULL; + entity player = M_ARGV(0, entity); + //if(STAT(FROZEN, player)) + //if(player.freezetag_frozen_timeout > 0 && time < player.freezetag_frozen_timeout) + //player.iceblock.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (player.freezetag_frozen_timeout - time) / (player.freezetag_frozen_timeout - player.freezetag_frozen_time); + + if(player.freezetag_frozen_timeout > 0 && time >= player.freezetag_frozen_timeout) + n = -1; + else + { + vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size; + n = 0; + FOREACH_CLIENT(IS_PLAYER(it) && it != player, { + if(STAT(FROZEN, it) == 0) + if(!IS_DEAD(it)) + if(SAME_TEAM(it, player)) + if(boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax)) + { + if(!o) + o = it; + if(STAT(FROZEN, player) == 1) + it.reviving = true; + ++n; + } + }); + + } + + if(n && STAT(FROZEN, player) == 1) // OK, there is at least one teammate reviving us + { + STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1); + player.health = max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health)); + + if(STAT(REVIVE_PROGRESS, player) >= 1) + { + freezetag_Unfreeze(player); + freezetag_count_alive_players(); + + if(n == -1) + { + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, player.netname, autocvar_g_freezetag_frozen_maxtime); + return true; + } + + // EVERY team mate nearby gets a point (even if multiple!) + FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, { + GameRules_scoring_add(it, FREEZETAG_REVIVALS, +1); + GameRules_scoring_add(it, SCORE, +1); + nades_GiveBonus(it,autocvar_g_nades_bonus_score_low); + }); + + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname); + Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED, player.netname, o.netname); + } + + FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, { + STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player); + it.reviving = false; + }); + } + else if(!n && STAT(FROZEN, player) == 1) // only if no teammate is nearby will we reset + { + STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) - frametime * autocvar_g_freezetag_revive_clearspeed, 1); + player.health = max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health)); + } + else if(!n && !STAT(FROZEN, player)) + { + STAT(REVIVE_PROGRESS, player) = 0; // thawing nobody + } + + return true; +} + +MUTATOR_HOOKFUNCTION(ft, SetStartItems) +{ + start_items &= ~IT_UNLIMITED_AMMO; + //start_health = warmup_start_health = cvar("g_lms_start_health"); + //start_armorvalue = warmup_start_armorvalue = cvar("g_lms_start_armor"); + start_ammo_shells = warmup_start_ammo_shells = cvar("g_lms_start_ammo_shells"); + start_ammo_nails = warmup_start_ammo_nails = cvar("g_lms_start_ammo_nails"); + start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets"); + start_ammo_cells = warmup_start_ammo_cells = cvar("g_lms_start_ammo_cells"); + start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_lms_start_ammo_plasma"); + start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_lms_start_ammo_fuel"); +} + +MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole) +{ + entity bot = M_ARGV(0, entity); + + if (!IS_DEAD(bot)) + { + if (random() < 0.5) + bot.havocbot_role = havocbot_role_ft_freeing; + else + bot.havocbot_role = havocbot_role_ft_offense; + } + + return true; +} + +MUTATOR_HOOKFUNCTION(ft, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE) +{ + M_ARGV(0, float) = freezetag_teams; +} + +MUTATOR_HOOKFUNCTION(ft, SetWeaponArena) +{ + // most weapons arena + if(M_ARGV(0, string) == "0" || M_ARGV(0, string) == "") + M_ARGV(0, string) = "most"; +} + +MUTATOR_HOOKFUNCTION(ft, FragCenterMessage) +{ + entity frag_attacker = M_ARGV(0, entity); + entity frag_target = M_ARGV(1, entity); + //float frag_deathtype = M_ARGV(2, float); + int kill_count_to_attacker = M_ARGV(3, int); + int kill_count_to_target = M_ARGV(4, int); + + if(STAT(FROZEN, frag_target)) + return; // target was already frozen, so this is just pushing them off the cliff + + Send_Notification(NOTIF_ONE, frag_attacker, MSG_CHOICE, CHOICE_FRAG_FREEZE, frag_target.netname, kill_count_to_attacker, (IS_BOT_CLIENT(frag_target) ? -1 : CS(frag_target).ping)); + Send_Notification(NOTIF_ONE, frag_target, MSG_CHOICE, CHOICE_FRAGGED_FREEZE, frag_attacker.netname, kill_count_to_target, frag_attacker.health, frag_attacker.armorvalue, (IS_BOT_CLIENT(frag_attacker) ? -1 : CS(frag_attacker).ping)); + + return true; +} + +void freezetag_Initialize() +{ + freezetag_teams = autocvar_g_freezetag_teams_override; + if(freezetag_teams < 2) + freezetag_teams = cvar("g_freezetag_teams"); // read the cvar directly as it gets written earlier in the same frame + + freezetag_teams = BITS(bound(2, freezetag_teams, 4)); + GameRules_scoring(freezetag_teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, { + field(SP_FREEZETAG_REVIVALS, "revivals", 0); + }); + + round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null); + round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit); + + EliminatedPlayers_Init(freezetag_isEliminated); +} +#endif diff --git a/qcsrc/common/gamemodes/gamemode/freezetag/freezetag.qh b/qcsrc/common/gamemodes/gamemode/freezetag/freezetag.qh new file mode 100644 index 000000000..ed38ae50a --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/freezetag/freezetag.qh @@ -0,0 +1,37 @@ +#pragma once + +#ifdef SVQC +#include +int autocvar_g_freezetag_point_limit; +int autocvar_g_freezetag_point_leadlimit; +bool autocvar_g_freezetag_team_spawns; +void freezetag_Initialize(); + +REGISTER_MUTATOR(ft, false) +{ + MUTATOR_STATIC(); + MUTATOR_ONADD + { + GameRules_teams(true); + GameRules_spawning_teams(autocvar_g_freezetag_team_spawns); + GameRules_limit_score(autocvar_g_freezetag_point_limit); + GameRules_limit_lead(autocvar_g_freezetag_point_leadlimit); + + freezetag_Initialize(); + } + return 0; +} + +.float freezetag_frozen_time; +.float freezetag_frozen_timeout; +const float ICE_MAX_ALPHA = 1; +const float ICE_MIN_ALPHA = 0.1; +float freezetag_teams; + +.float reviving; // temp var + +float autocvar_g_freezetag_revive_extra_size; +float autocvar_g_freezetag_revive_speed; +bool autocvar_g_freezetag_revive_nade; +float autocvar_g_freezetag_revive_nade_health; +#endif diff --git a/qcsrc/common/gamemodes/gamemode/invasion/_mod.inc b/qcsrc/common/gamemodes/gamemode/invasion/_mod.inc new file mode 100644 index 000000000..905aa0611 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/invasion/_mod.inc @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/invasion/_mod.qh b/qcsrc/common/gamemodes/gamemode/invasion/_mod.qh new file mode 100644 index 000000000..d8e8d223d --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/invasion/_mod.qh @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/invasion/invasion.qc b/qcsrc/common/gamemodes/gamemode/invasion/invasion.qc new file mode 100644 index 000000000..d6f9860c9 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/invasion/invasion.qc @@ -0,0 +1,606 @@ +#include "invasion.qh" + +// TODO: sv_invasion +#ifdef SVQC +#include +#include +#include + +#include + +IntrusiveList g_invasion_roundends; +IntrusiveList g_invasion_waves; +IntrusiveList g_invasion_spawns; +STATIC_INIT(g_invasion) +{ + g_invasion_roundends = IL_NEW(); + g_invasion_waves = IL_NEW(); + g_invasion_spawns = IL_NEW(); +} + +float autocvar_g_invasion_round_timelimit; +float autocvar_g_invasion_spawnpoint_spawn_delay; +float autocvar_g_invasion_warmup; +int autocvar_g_invasion_monster_count; +bool autocvar_g_invasion_zombies_only; +float autocvar_g_invasion_spawn_delay; + +bool victent_present; +.bool inv_endreached; + +bool inv_warning_shown; // spammy + +void target_invasion_roundend_use(entity this, entity actor, entity trigger) +{ + if(!IS_PLAYER(actor)) { return; } + + actor.inv_endreached = true; + + int plnum = 0; + int realplnum = 0; + // let's not count bots + FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { + ++realplnum; + if(it.inv_endreached) + ++plnum; + }); + if(plnum < ceil(realplnum * min(1, this.count))) // 70% of players + return; + + this.winning = true; +} + +spawnfunc(target_invasion_roundend) +{ + if(!g_invasion) { delete(this); return; } + + victent_present = true; // a victory entity is present, we don't need to rely on monster count TODO: merge this with the intrusive list (can check empty) + + if(!this.count) { this.count = 0.7; } // require at least 70% of the players to reach the end before triggering victory + + this.use = target_invasion_roundend_use; + + IL_PUSH(g_invasion_roundends, this); +} + +spawnfunc(invasion_wave) +{ + if(!g_invasion) { delete(this); return; } + + IL_PUSH(g_invasion_waves, this); +} + +spawnfunc(invasion_spawnpoint) +{ + if(!g_invasion) { delete(this); return; } + + this.classname = "invasion_spawnpoint"; + IL_PUSH(g_invasion_spawns, this); +} + +void ClearWinners(); + +// Invasion stage mode winning condition: If the attackers triggered a round end (by fulfilling all objectives) +// they win. +int WinningCondition_Invasion() +{ + WinningConditionHelper(NULL); // set worldstatus + + int status = WINNING_NO; + + if(autocvar_g_invasion_type == INV_TYPE_STAGE) + { + SetWinners(inv_endreached, true); + + int found = 0; + IL_EACH(g_invasion_roundends, true, + { + ++found; + if(it.winning) + { + bprint("Invasion: round completed.\n"); + // winners already set (TODO: teamplay support) + + status = WINNING_YES; + break; + } + }); + + if(!found) + status = WINNING_YES; // just end it? TODO: should warn mapper! + } + else if(autocvar_g_invasion_type == INV_TYPE_HUNT) + { + ClearWinners(); + + int found = 0; // NOTE: this ends the round if no monsters are placed + IL_EACH(g_monsters, !(it.spawnflags & MONSTERFLAG_RESPAWNED), + { + ++found; + }); + + if(found <= 0) + { + FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), + { + it.winning = true; + }); + status = WINNING_YES; + } + } + + return status; +} + +Monster invasion_PickMonster(int supermonster_count) +{ + RandomSelection_Init(); + + FOREACH(Monsters, it != MON_Null, + { + if((it.spawnflags & MON_FLAG_HIDDEN) || (it.spawnflags & MONSTER_TYPE_PASSIVE) || (it.spawnflags & MONSTER_TYPE_FLY) || (it.spawnflags & MONSTER_TYPE_SWIM) || + (it.spawnflags & MONSTER_SIZE_QUAKE) || ((it.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1)) + continue; + if(autocvar_g_invasion_zombies_only && !(it.spawnflags & MONSTER_TYPE_UNDEAD)) + continue; + RandomSelection_AddEnt(it, 1, 1); + }); + + return RandomSelection_chosen_ent; +} + +entity invasion_PickSpawn() +{ + RandomSelection_Init(); + + IL_EACH(g_invasion_spawns, true, + { + RandomSelection_AddEnt(it, 1, ((time < it.spawnshieldtime) ? 0.2 : 1)); // give recently used spawnpoints a very low rating + it.spawnshieldtime = time + autocvar_g_invasion_spawnpoint_spawn_delay; + }); + + return RandomSelection_chosen_ent; +} + +entity invasion_GetWaveEntity(int wavenum) +{ + IL_EACH(g_invasion_waves, it.cnt == wavenum, + { + return it; // found one + }); + + // if no specific one is found, find the last existing wave ent + entity best = NULL; + IL_EACH(g_invasion_waves, it.cnt <= wavenum, + { + if(!best || it.cnt > best.cnt) + best = it; + }); + + return best; +} + +void invasion_SpawnChosenMonster(Monster mon) +{ + entity monster; + entity spawn_point = invasion_PickSpawn(); + entity wave_ent = invasion_GetWaveEntity(inv_roundcnt); + + string tospawn = ""; + if(wave_ent && wave_ent.spawnmob && wave_ent.spawnmob != "") + { + RandomSelection_Init(); + FOREACH_WORD(wave_ent.spawnmob, true, + { + RandomSelection_AddString(it, 1, 1); + }); + + tospawn = RandomSelection_chosen_string; + } + + if(spawn_point == NULL) + { + if(!inv_warning_shown) + { + inv_warning_shown = true; + LOG_TRACE("Warning: couldn't find any invasion_spawnpoint spawnpoints, attempting to spawn monsters in random locations"); + } + entity e = spawn(); + setsize(e, mon.m_mins, mon.m_maxs); + + if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256)) + monster = spawnmonster(e, tospawn, mon.monsterid, NULL, NULL, e.origin, false, false, 2); + else + { + delete(e); + return; + } + } + else // if spawnmob field falls through (unset), fallback to mon (relying on spawnmonster for that behaviour) + monster = spawnmonster(spawn(), ((spawn_point.spawnmob && spawn_point.spawnmob != "") ? spawn_point.spawnmob : tospawn), mon.monsterid, spawn_point, spawn_point, spawn_point.origin, false, false, 2); + + if(!monster) + return; + + monster.spawnshieldtime = time; + + if(spawn_point) + { + if(spawn_point.target_range) + monster.target_range = spawn_point.target_range; + monster.target2 = spawn_point.target2; + } + + if(teamplay) + { + if(spawn_point && spawn_point.team && inv_monsters_perteam[spawn_point.team] > 0) + monster.team = spawn_point.team; + else + { + RandomSelection_Init(); + if(inv_monsters_perteam[NUM_TEAM_1] > 0) RandomSelection_AddFloat(NUM_TEAM_1, 1, 1); + if(inv_monsters_perteam[NUM_TEAM_2] > 0) RandomSelection_AddFloat(NUM_TEAM_2, 1, 1); + if(invasion_teams >= 3) if(inv_monsters_perteam[NUM_TEAM_3] > 0) { RandomSelection_AddFloat(NUM_TEAM_3, 1, 1); } + if(invasion_teams >= 4) if(inv_monsters_perteam[NUM_TEAM_4] > 0) { RandomSelection_AddFloat(NUM_TEAM_4, 1, 1); } + + monster.team = RandomSelection_chosen_float; + } + + monster_setupcolors(monster); + + if(monster.sprite) + { + WaypointSprite_UpdateTeamRadar(monster.sprite, RADARICON_DANGER, ((monster.team) ? Team_ColorRGB(monster.team) : '1 0 0')); + + monster.sprite.team = 0; + monster.sprite.SendFlags |= 1; + } + } + + if(monster.monster_attack) + IL_REMOVE(g_monster_targets, monster); + monster.monster_attack = false; // it's the player's job to kill all the monsters + + if(inv_roundcnt >= inv_maxrounds) + monster.spawnflags |= MONSTERFLAG_MINIBOSS; // last round spawns minibosses +} + +void invasion_SpawnMonsters(int supermonster_count) +{ + Monster chosen_monster = invasion_PickMonster(supermonster_count); + + invasion_SpawnChosenMonster(chosen_monster); +} + +bool Invasion_CheckWinner() +{ + if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) + { + IL_EACH(g_monsters, true, + { + Monster_Remove(it); + }); + IL_CLEAR(g_monsters); + + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER); + round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit); + return 1; + } + + float total_alive_monsters = 0, supermonster_count = 0, red_alive = 0, blue_alive = 0, yellow_alive = 0, pink_alive = 0; + + IL_EACH(g_monsters, it.health > 0, + { + if((get_monsterinfo(it.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER) + ++supermonster_count; + ++total_alive_monsters; + + if(teamplay) + switch(it.team) + { + case NUM_TEAM_1: ++red_alive; break; + case NUM_TEAM_2: ++blue_alive; break; + case NUM_TEAM_3: ++yellow_alive; break; + case NUM_TEAM_4: ++pink_alive; break; + } + }); + + if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < inv_maxspawned) + { + if(time >= inv_lastcheck) + { + invasion_SpawnMonsters(supermonster_count); + inv_lastcheck = time + autocvar_g_invasion_spawn_delay; + } + + return 0; + } + + if(inv_numspawned < 1) + return 0; // nothing has spawned yet + + if(teamplay) + { + if(((red_alive > 0) + (blue_alive > 0) + (yellow_alive > 0) + (pink_alive > 0)) > 1) + return 0; + } + else if(inv_numkilled < inv_maxspawned) + return 0; + + entity winner = NULL; + float winning_score = 0, winner_team = 0; + + + if(teamplay) + { + if(red_alive > 0) { winner_team = NUM_TEAM_1; } + if(blue_alive > 0) + if(winner_team) { winner_team = 0; } + else { winner_team = NUM_TEAM_2; } + if(yellow_alive > 0) + if(winner_team) { winner_team = 0; } + else { winner_team = NUM_TEAM_3; } + if(pink_alive > 0) + if(winner_team) { winner_team = 0; } + else { winner_team = NUM_TEAM_4; } + } + else + { + FOREACH_CLIENT(IS_PLAYER(it), { + float cs = GameRules_scoring_add(it, KILLS, 0); + if(cs > winning_score) + { + winning_score = cs; + winner = it; + } + }); + } + + IL_EACH(g_monsters, true, + { + Monster_Remove(it); + }); + IL_CLEAR(g_monsters); + + if(teamplay) + { + if(winner_team) + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN)); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN)); + } + } + else if(winner) + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname); + } + + round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit); + + return 1; +} + +bool Invasion_CheckPlayers() +{ + return true; +} + +void Invasion_RoundStart() +{ + int numplayers = 0; + FOREACH_CLIENT(IS_PLAYER(it), { + it.player_blocked = false; + ++numplayers; + }); + + if(inv_roundcnt < inv_maxrounds) + inv_roundcnt += 1; // a limiter to stop crazy counts + + inv_monsterskill = inv_roundcnt + max(1, numplayers * 0.3); + + inv_maxcurrent = 0; + inv_numspawned = 0; + inv_numkilled = 0; + + inv_maxspawned = rint(max(autocvar_g_invasion_monster_count, autocvar_g_invasion_monster_count * (inv_roundcnt * 0.5))); + + if(teamplay) + { + DistributeEvenly_Init(inv_maxspawned, invasion_teams); + inv_monsters_perteam[NUM_TEAM_1] = DistributeEvenly_Get(1); + inv_monsters_perteam[NUM_TEAM_2] = DistributeEvenly_Get(1); + if(invasion_teams >= 3) inv_monsters_perteam[NUM_TEAM_3] = DistributeEvenly_Get(1); + if(invasion_teams >= 4) inv_monsters_perteam[NUM_TEAM_4] = DistributeEvenly_Get(1); + } +} + +MUTATOR_HOOKFUNCTION(inv, MonsterDies) +{ + entity frag_target = M_ARGV(0, entity); + entity frag_attacker = M_ARGV(1, entity); + + if(!(frag_target.spawnflags & MONSTERFLAG_RESPAWNED)) + { + if(autocvar_g_invasion_type == INV_TYPE_ROUND) + { + inv_numkilled += 1; + inv_maxcurrent -= 1; + } + if(teamplay) { inv_monsters_perteam[frag_target.team] -= 1; } + + if(IS_PLAYER(frag_attacker)) + if(SAME_TEAM(frag_attacker, frag_target)) // in non-teamplay modes, same team = same player, so this works + GameRules_scoring_add(frag_attacker, KILLS, -1); + else + { + GameRules_scoring_add(frag_attacker, KILLS, +1); + if(teamplay) + TeamScore_AddToTeam(frag_attacker.team, ST_INV_KILLS, +1); + } + } +} + +MUTATOR_HOOKFUNCTION(inv, MonsterSpawn) +{ + entity mon = M_ARGV(0, entity); + mon.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP; + + if(autocvar_g_invasion_type == INV_TYPE_HUNT) + return false; // allowed + + if(!(mon.spawnflags & MONSTERFLAG_SPAWNED)) + return true; + + if(!(mon.spawnflags & MONSTERFLAG_RESPAWNED)) + { + inv_numspawned += 1; + inv_maxcurrent += 1; + } + + mon.monster_skill = inv_monsterskill; + + if((get_monsterinfo(mon.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER) + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, mon.monster_name); +} + +MUTATOR_HOOKFUNCTION(inv, SV_StartFrame) +{ + if(autocvar_g_invasion_type != INV_TYPE_ROUND) + return; // uses map spawned monsters + + monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned + monsters_killed = inv_numkilled; +} + +MUTATOR_HOOKFUNCTION(inv, PlayerRegen) +{ + // no regeneration in invasion, regardless of the game type + return true; +} + +MUTATOR_HOOKFUNCTION(inv, PlayerSpawn) +{ + entity player = M_ARGV(0, entity); + + if(player.bot_attack) + IL_REMOVE(g_bot_targets, player); + player.bot_attack = false; +} + +MUTATOR_HOOKFUNCTION(inv, Damage_Calculate) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + float frag_damage = M_ARGV(4, float); + vector frag_force = M_ARGV(6, vector); + + if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker != frag_target) + { + frag_damage = 0; + frag_force = '0 0 0'; + + M_ARGV(4, float) = frag_damage; + M_ARGV(6, vector) = frag_force; + } +} + +MUTATOR_HOOKFUNCTION(inv, BotShouldAttack) +{ + entity targ = M_ARGV(1, entity); + + if(!IS_MONSTER(targ)) + return true; +} + +MUTATOR_HOOKFUNCTION(inv, SetStartItems) +{ + if(autocvar_g_invasion_type == INV_TYPE_ROUND) + { + start_health = 200; + start_armorvalue = 200; + } +} + +MUTATOR_HOOKFUNCTION(inv, AccuracyTargetValid) +{ + entity frag_target = M_ARGV(1, entity); + + if(IS_MONSTER(frag_target)) + return MUT_ACCADD_INVALID; + return MUT_ACCADD_INDIFFERENT; +} + +MUTATOR_HOOKFUNCTION(inv, AllowMobSpawning) +{ + // monster spawning disabled during an invasion + M_ARGV(1, string) = "You cannot spawn monsters during an invasion!"; + return true; +} + +MUTATOR_HOOKFUNCTION(inv, CheckRules_World) +{ + if(autocvar_g_invasion_type == INV_TYPE_ROUND) + return false; + + M_ARGV(0, float) = WinningCondition_Invasion(); + return true; +} + +MUTATOR_HOOKFUNCTION(inv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE) +{ + M_ARGV(0, float) = invasion_teams; +} + +MUTATOR_HOOKFUNCTION(inv, AllowMobButcher) +{ + M_ARGV(0, string) = "This command does not work during an invasion!"; + return true; +} + +void invasion_ScoreRules(int inv_teams) +{ + if(inv_teams) { CheckAllowedTeams(NULL); } + GameRules_score_enabled(false); + GameRules_scoring(inv_teams, 0, 0, { + if (inv_teams) { + field_team(ST_INV_KILLS, "frags", SFL_SORT_PRIO_PRIMARY); + } + field(SP_KILLS, "frags", ((inv_teams) ? SFL_SORT_PRIO_SECONDARY : SFL_SORT_PRIO_PRIMARY)); + }); +} + +void invasion_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up. +{ + if(autocvar_g_invasion_type == INV_TYPE_HUNT || autocvar_g_invasion_type == INV_TYPE_STAGE) + cvar_set("fraglimit", "0"); + + if(autocvar_g_invasion_teams) + { + invasion_teams = BITS(bound(2, autocvar_g_invasion_teams, 4)); + } + else + invasion_teams = 0; + + independent_players = 1; // to disable extra useless scores + + invasion_ScoreRules(invasion_teams); + + independent_players = 0; + + if(autocvar_g_invasion_type == INV_TYPE_ROUND) + { + round_handler_Spawn(Invasion_CheckPlayers, Invasion_CheckWinner, Invasion_RoundStart); + round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit); + + inv_roundcnt = 0; + inv_maxrounds = 15; // 15? + } +} + +void invasion_Initialize() +{ + InitializeEntity(NULL, invasion_DelayedInit, INITPRIO_GAMETYPE); +} +#endif diff --git a/qcsrc/common/gamemodes/gamemode/invasion/invasion.qh b/qcsrc/common/gamemodes/gamemode/invasion/invasion.qh new file mode 100644 index 000000000..85cd7ec09 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/invasion/invasion.qh @@ -0,0 +1,48 @@ +#pragma once + +#ifdef SVQC +#include +#define autocvar_g_invasion_point_limit cvar("g_invasion_point_limit") +int autocvar_g_invasion_teams; +int autocvar_g_invasion_type; +bool autocvar_g_invasion_team_spawns; +bool g_invasion; +void invasion_Initialize(); + +REGISTER_MUTATOR(inv, false) +{ + MUTATOR_STATIC(); + MUTATOR_ONADD + { + if (autocvar_g_invasion_teams >= 2) { + GameRules_teams(true); + GameRules_spawning_teams(autocvar_g_invasion_team_spawns); + } + GameRules_limit_score(autocvar_g_invasion_point_limit); + + g_invasion = true; + cvar_settemp("g_monsters", "1"); + invasion_Initialize(); + } + return 0; +} + +float inv_numspawned; +float inv_maxspawned; +float inv_roundcnt; +float inv_maxrounds; +float inv_numkilled; +float inv_lastcheck; +float inv_maxcurrent; + +float invasion_teams; +float inv_monsters_perteam[17]; + +float inv_monsterskill; + +const float ST_INV_KILLS = 1; + +const int INV_TYPE_ROUND = 0; // round-based waves of enemies +const int INV_TYPE_HUNT = 1; // clear the map of placed enemies +const int INV_TYPE_STAGE = 2; // reach the end of the level +#endif diff --git a/qcsrc/common/gamemodes/gamemode/keepaway/_mod.inc b/qcsrc/common/gamemodes/gamemode/keepaway/_mod.inc new file mode 100644 index 000000000..9426d7807 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/keepaway/_mod.inc @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/keepaway/_mod.qh b/qcsrc/common/gamemodes/gamemode/keepaway/_mod.qh new file mode 100644 index 000000000..32872a2a6 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/keepaway/_mod.qh @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/keepaway/keepaway.qc b/qcsrc/common/gamemodes/gamemode/keepaway/keepaway.qc new file mode 100644 index 000000000..8eb88a3bb --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/keepaway/keepaway.qc @@ -0,0 +1,475 @@ +#include "keepaway.qh" + +// TODO: keepaway +#ifdef SVQC +#include + +.entity ballcarried; + +int autocvar_g_keepaway_ballcarrier_effects; +float autocvar_g_keepaway_ballcarrier_damage; +float autocvar_g_keepaway_ballcarrier_force; +float autocvar_g_keepaway_ballcarrier_highspeed; +float autocvar_g_keepaway_ballcarrier_selfdamage; +float autocvar_g_keepaway_ballcarrier_selfforce; +float autocvar_g_keepaway_noncarrier_damage; +float autocvar_g_keepaway_noncarrier_force; +float autocvar_g_keepaway_noncarrier_selfdamage; +float autocvar_g_keepaway_noncarrier_selfforce; +bool autocvar_g_keepaway_noncarrier_warn; +int autocvar_g_keepaway_score_bckill; +int autocvar_g_keepaway_score_killac; +int autocvar_g_keepaway_score_timepoints; +float autocvar_g_keepaway_score_timeinterval; +float autocvar_g_keepawayball_damageforcescale; +int autocvar_g_keepawayball_effects; +float autocvar_g_keepawayball_respawntime; +int autocvar_g_keepawayball_trail_color; + +bool ka_ballcarrier_waypointsprite_visible_for_player(entity this, entity player, entity view) // runs on waypoints which are attached to ballcarriers, updates once per frame +{ + if(view.ballcarried) + if(IS_SPEC(player)) + return false; // we don't want spectators of the ballcarrier to see the attached waypoint on the top of their screen + + // TODO: Make the ballcarrier lack a waypointsprite whenever they have the invisibility powerup + + return true; +} + +void ka_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later +{ + if(autocvar_sv_eventlog) + GameLogEcho(strcat(":ka:", mode, ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : ""))); +} + +void ka_TouchEvent(entity this, entity toucher); +void ka_RespawnBall(entity this) // runs whenever the ball needs to be relocated +{ + if(game_stopped) return; + vector oldballorigin = this.origin; + + if(!MoveToRandomMapLocation(this, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256)) + { + entity spot = SelectSpawnPoint(this, true); + setorigin(this, spot.origin); + this.angles = spot.angles; + } + + makevectors(this.angles); + set_movetype(this, MOVETYPE_BOUNCE); + this.velocity = '0 0 200'; + this.angles = '0 0 0'; + this.effects = autocvar_g_keepawayball_effects; + settouch(this, ka_TouchEvent); + setthink(this, ka_RespawnBall); + this.nextthink = time + autocvar_g_keepawayball_respawntime; + navigation_dynamicgoal_set(this); + + Send_Effect(EFFECT_ELECTRO_COMBO, oldballorigin, '0 0 0', 1); + Send_Effect(EFFECT_ELECTRO_COMBO, this.origin, '0 0 0', 1); + + WaypointSprite_Spawn(WP_KaBall, 0, 0, this, '0 0 64', NULL, this.team, this, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER); + WaypointSprite_Ping(this.waypointsprite_attachedforcarrier); + + sound(this, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere) +} + +void ka_TimeScoring(entity this) +{ + if(this.owner.ballcarried) + { // add points for holding the ball after a certain amount of time + if(autocvar_g_keepaway_score_timepoints) + GameRules_scoring_add(this.owner, SCORE, autocvar_g_keepaway_score_timepoints); + + GameRules_scoring_add(this.owner, KEEPAWAY_BCTIME, (autocvar_g_keepaway_score_timeinterval / 1)); // interval is divided by 1 so that time always shows "seconds" + this.nextthink = time + autocvar_g_keepaway_score_timeinterval; + } +} + +void ka_TouchEvent(entity this, entity toucher) // runs any time that the ball comes in contact with something +{ + if(game_stopped) return; + if(!this) return; + if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) + { // The ball fell off the map, respawn it since players can't get to it + ka_RespawnBall(this); + return; + } + if(IS_DEAD(toucher)) { return; } + if(STAT(FROZEN, toucher)) { return; } + if (!IS_PLAYER(toucher)) + { // The ball just touched an object, most likely the world + Send_Effect(EFFECT_BALL_SPARKS, this.origin, '0 0 0', 1); + sound(this, CH_TRIGGER, SND_KA_TOUCH, VOL_BASE, ATTEN_NORM); + return; + } + else if(this.wait > time) { return; } + + // attach the ball to the player + this.owner = toucher; + toucher.ballcarried = this; + GameRules_scoring_vip(toucher, true); + setattachment(this, toucher, ""); + setorigin(this, '0 0 0'); + + // make the ball invisible/unable to do anything/set up time scoring + this.velocity = '0 0 0'; + set_movetype(this, MOVETYPE_NONE); + this.effects |= EF_NODRAW; + settouch(this, func_null); + setthink(this, ka_TimeScoring); + this.nextthink = time + autocvar_g_keepaway_score_timeinterval; + this.takedamage = DAMAGE_NO; + navigation_dynamicgoal_unset(this); + + // apply effects to player + toucher.glow_color = autocvar_g_keepawayball_trail_color; + toucher.glow_trail = true; + toucher.effects |= autocvar_g_keepaway_ballcarrier_effects; + + // messages and sounds + ka_EventLog("pickup", toucher); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_KEEPAWAY_PICKUP, toucher.netname); + Send_Notification(NOTIF_ALL_EXCEPT, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP, toucher.netname); + Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP_SELF); + sound(this.owner, CH_TRIGGER, SND_KA_PICKEDUP, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere) + + // scoring + GameRules_scoring_add(toucher, KEEPAWAY_PICKUPS, 1); + + // waypoints + WaypointSprite_AttachCarrier(WP_KaBallCarrier, toucher, RADARICON_FLAGCARRIER); + toucher.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = ka_ballcarrier_waypointsprite_visible_for_player; + WaypointSprite_UpdateRule(toucher.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT); + WaypointSprite_Ping(toucher.waypointsprite_attachedforcarrier); + WaypointSprite_Kill(this.waypointsprite_attachedforcarrier); +} + +void ka_DropEvent(entity plyr) // runs any time that a player is supposed to lose the ball +{ + entity ball; + ball = plyr.ballcarried; + + if(!ball) { return; } + + // reset the ball + setattachment(ball, NULL, ""); + set_movetype(ball, MOVETYPE_BOUNCE); + ball.wait = time + 1; + settouch(ball, ka_TouchEvent); + setthink(ball, ka_RespawnBall); + ball.nextthink = time + autocvar_g_keepawayball_respawntime; + ball.takedamage = DAMAGE_YES; + ball.effects &= ~EF_NODRAW; + setorigin(ball, plyr.origin + '0 0 10'); + ball.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom(); + entity e = ball.owner; ball.owner = NULL; + e.ballcarried = NULL; + GameRules_scoring_vip(e, false); + navigation_dynamicgoal_set(ball); + + // reset the player effects + plyr.glow_trail = false; + plyr.effects &= ~autocvar_g_keepaway_ballcarrier_effects; + + // messages and sounds + ka_EventLog("dropped", plyr); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_KEEPAWAY_DROPPED, plyr.netname); + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEEPAWAY_DROPPED, plyr.netname); + sound(NULL, CH_TRIGGER, SND_KA_DROPPED, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere) + + // scoring + // GameRules_scoring_add(plyr, KEEPAWAY_DROPS, 1); Not anymore, this is 100% the same as pickups and is useless. + + // waypoints + WaypointSprite_Spawn(WP_KaBall, 0, 0, ball, '0 0 64', NULL, ball.team, ball, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER); + WaypointSprite_UpdateRule(ball.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT); + WaypointSprite_Ping(ball.waypointsprite_attachedforcarrier); + WaypointSprite_Kill(plyr.waypointsprite_attachedforcarrier); +} + +/** used to clear the ballcarrier whenever the match switches from warmup to normal */ +void ka_Reset(entity this) +{ + if((this.owner) && (IS_PLAYER(this.owner))) + ka_DropEvent(this.owner); + + if(time < game_starttime) + { + setthink(this, ka_RespawnBall); + settouch(this, func_null); + this.nextthink = game_starttime; + } + else + ka_RespawnBall(this); +} + + +// ================ +// Bot player logic +// ================ + +void havocbot_goalrating_ball(entity this, float ratingscale, vector org) +{ + float t; + entity ball_owner; + ball_owner = ka_ball.owner; + + if (ball_owner == this) + return; + + // If ball is carried by player then hunt them down. + if (ball_owner) + { + t = (this.health + this.armorvalue) / (ball_owner.health + ball_owner.armorvalue); + navigation_routerating(this, ball_owner, t * ratingscale, 2000); + } + else // Ball has been dropped so collect. + navigation_routerating(this, ka_ball, ratingscale, 2000); +} + +void havocbot_role_ka_carrier(entity this) +{ + if (IS_DEAD(this)) + return; + + if (navigation_goalrating_timeout(this)) + { + navigation_goalrating_start(this); + havocbot_goalrating_items(this, 10000, this.origin, 10000); + havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000); + havocbot_goalrating_waypoints(this, 1, this.origin, 3000); + navigation_goalrating_end(this); + + navigation_goalrating_timeout_set(this); + } + + if (!this.ballcarried) + { + this.havocbot_role = havocbot_role_ka_collector; + navigation_goalrating_timeout_expire(this, 2); + } +} + +void havocbot_role_ka_collector(entity this) +{ + if (IS_DEAD(this)) + return; + + if (navigation_goalrating_timeout(this)) + { + navigation_goalrating_start(this); + havocbot_goalrating_items(this, 10000, this.origin, 10000); + havocbot_goalrating_enemyplayers(this, 1000, this.origin, 10000); + havocbot_goalrating_ball(this, 20000, this.origin); + navigation_goalrating_end(this); + + navigation_goalrating_timeout_set(this); + } + + if (this.ballcarried) + { + this.havocbot_role = havocbot_role_ka_carrier; + navigation_goalrating_timeout_expire(this, 2); + } +} + + +// ============== +// Hook Functions +// ============== + +MUTATOR_HOOKFUNCTION(ka, PlayerDies) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + + if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker))) + { + if(frag_target.ballcarried) { // add to amount of times killing carrier + GameRules_scoring_add(frag_attacker, KEEPAWAY_CARRIERKILLS, 1); + if(autocvar_g_keepaway_score_bckill) // add bckills to the score + GameRules_scoring_add(frag_attacker, SCORE, autocvar_g_keepaway_score_bckill); + } + else if(!frag_attacker.ballcarried) + if(autocvar_g_keepaway_noncarrier_warn) + Send_Notification(NOTIF_ONE_ONLY, frag_attacker, MSG_CENTER, CENTER_KEEPAWAY_WARN); + + if(frag_attacker.ballcarried) // add to amount of kills while ballcarrier + GameRules_scoring_add(frag_attacker, SCORE, autocvar_g_keepaway_score_killac); + } + + if(frag_target.ballcarried) { ka_DropEvent(frag_target); } // a player with the ball has died, drop it +} + +MUTATOR_HOOKFUNCTION(ka, GiveFragsForKill) +{ + M_ARGV(2, float) = 0; // no frags counted in keepaway + return true; // you deceptive little bugger ;3 This needs to be true in order for this function to even count. +} + +MUTATOR_HOOKFUNCTION(ka, PlayerPreThink) +{ + entity player = M_ARGV(0, entity); + + // clear the item used for the ball in keepaway + player.items &= ~IT_KEY1; + + // if the player has the ball, make sure they have the item for it (Used for HUD primarily) + if(player.ballcarried) + player.items |= IT_KEY1; +} + +MUTATOR_HOOKFUNCTION(ka, PlayerUseKey) +{ + entity player = M_ARGV(0, entity); + + if(MUTATOR_RETURNVALUE == 0) + if(player.ballcarried) + { + ka_DropEvent(player); + return true; + } +} + +MUTATOR_HOOKFUNCTION(ka, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + float frag_damage = M_ARGV(4, float); + vector frag_force = M_ARGV(6, vector); + + if(frag_attacker.ballcarried) // if the attacker is a ballcarrier + { + if(frag_target == frag_attacker) // damage done to yourself + { + frag_damage *= autocvar_g_keepaway_ballcarrier_selfdamage; + frag_force *= autocvar_g_keepaway_ballcarrier_selfforce; + } + else // damage done to noncarriers + { + frag_damage *= autocvar_g_keepaway_ballcarrier_damage; + frag_force *= autocvar_g_keepaway_ballcarrier_force; + } + } + else if (!frag_target.ballcarried) // if the target is a noncarrier + { + if(frag_target == frag_attacker) // damage done to yourself + { + frag_damage *= autocvar_g_keepaway_noncarrier_selfdamage; + frag_force *= autocvar_g_keepaway_noncarrier_selfforce; + } + else // damage done to other noncarriers + { + frag_damage *= autocvar_g_keepaway_noncarrier_damage; + frag_force *= autocvar_g_keepaway_noncarrier_force; + } + } + + M_ARGV(4, float) = frag_damage; + M_ARGV(6, vector) = frag_force; +} + +MUTATOR_HOOKFUNCTION(ka, ClientDisconnect) +{ + entity player = M_ARGV(0, entity); + + if(player.ballcarried) { ka_DropEvent(player); } // a player with the ball has left the match, drop it +} + +MUTATOR_HOOKFUNCTION(ka, MakePlayerObserver) +{ + entity player = M_ARGV(0, entity); + + if(player.ballcarried) { ka_DropEvent(player); } // a player with the ball has left the match, drop it +} + +MUTATOR_HOOKFUNCTION(ka, PlayerPowerups) +{ + entity player = M_ARGV(0, entity); + + // In the future this hook is supposed to allow me to do some extra stuff with waypointsprites and invisibility powerup + // So bare with me until I can fix a certain bug with ka_ballcarrier_waypointsprite_visible_for_player() + + player.effects &= ~autocvar_g_keepaway_ballcarrier_effects; + + if(player.ballcarried) + player.effects |= autocvar_g_keepaway_ballcarrier_effects; +} + + +MUTATOR_HOOKFUNCTION(ka, PlayerPhysics_UpdateStats) +{ + entity player = M_ARGV(0, entity); + // these automatically reset, no need to worry + + if(player.ballcarried) + STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_keepaway_ballcarrier_highspeed; +} + +MUTATOR_HOOKFUNCTION(ka, BotShouldAttack) +{ + entity bot = M_ARGV(0, entity); + entity targ = M_ARGV(1, entity); + + // if neither player has ball then don't attack unless the ball is on the ground + if(!targ.ballcarried && !bot.ballcarried && ka_ball.owner) + return true; +} + +MUTATOR_HOOKFUNCTION(ka, HavocBot_ChooseRole) +{ + entity bot = M_ARGV(0, entity); + + if (bot.ballcarried) + bot.havocbot_role = havocbot_role_ka_carrier; + else + bot.havocbot_role = havocbot_role_ka_collector; + return true; +} + +MUTATOR_HOOKFUNCTION(ka, DropSpecialItems) +{ + entity frag_target = M_ARGV(0, entity); + + if(frag_target.ballcarried) + ka_DropEvent(frag_target); +} + +.bool pushable; + +// ============== +// Initialization +// ============== + +MODEL(KA_BALL, "models/orbs/orbblue.md3"); + +void ka_SpawnBall() // loads various values for the ball, runs only once at start of match +{ + entity e = new(keepawayball); + setmodel(e, MDL_KA_BALL); + setsize(e, '-16 -16 -20', '16 16 20'); // 20 20 20 was too big, player is only 16 16 24... gotta cheat with the Z (20) axis so that the particle isn't cut off + e.damageforcescale = autocvar_g_keepawayball_damageforcescale; + e.takedamage = DAMAGE_YES; + e.solid = SOLID_TRIGGER; + set_movetype(e, MOVETYPE_BOUNCE); + e.glow_color = autocvar_g_keepawayball_trail_color; + e.glow_trail = true; + e.flags = FL_ITEM; + IL_PUSH(g_items, e); + e.pushable = true; + e.reset = ka_Reset; + settouch(e, ka_TouchEvent); + e.owner = NULL; + ka_ball = e; + navigation_dynamicgoal_init(ka_ball, false); + + InitializeEntity(e, ka_RespawnBall, INITPRIO_SETLOCATION); // is this the right priority? Neh, I have no idea.. Well-- it works! So. +} + +void ka_Initialize() // run at the start of a match, initiates game mode +{ + ka_SpawnBall(); +} +#endif diff --git a/qcsrc/common/gamemodes/gamemode/keepaway/keepaway.qh b/qcsrc/common/gamemodes/gamemode/keepaway/keepaway.qh new file mode 100644 index 000000000..a4615c146 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/keepaway/keepaway.qh @@ -0,0 +1,31 @@ +#pragma once + +#ifdef SVQC +#include +#include +void ka_Initialize(); + +REGISTER_MUTATOR(ka, false) +{ + MUTATOR_STATIC(); + MUTATOR_ONADD + { + GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, { + field(SP_KEEPAWAY_PICKUPS, "pickups", 0); + field(SP_KEEPAWAY_CARRIERKILLS, "bckills", 0); + field(SP_KEEPAWAY_BCTIME, "bctime", SFL_SORT_PRIO_SECONDARY); + }); + + ka_Initialize(); + } + return false; +} + + +entity ka_ball; + +void(entity this) havocbot_role_ka_carrier; +void(entity this) havocbot_role_ka_collector; + +void ka_DropEvent(entity plyr); +#endif diff --git a/qcsrc/common/gamemodes/gamemode/keyhunt/_mod.inc b/qcsrc/common/gamemodes/gamemode/keyhunt/_mod.inc new file mode 100644 index 000000000..3861dea00 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/keyhunt/_mod.inc @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/keyhunt/_mod.qh b/qcsrc/common/gamemodes/gamemode/keyhunt/_mod.qh new file mode 100644 index 000000000..cd796c7ef --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/keyhunt/_mod.qh @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/keyhunt/keyhunt.qc b/qcsrc/common/gamemodes/gamemode/keyhunt/keyhunt.qc new file mode 100644 index 000000000..6523612e2 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/keyhunt/keyhunt.qc @@ -0,0 +1,1324 @@ +#include "keyhunt.qh" + +// TODO: sv_keyhunt +#ifdef SVQC +float autocvar_g_balance_keyhunt_damageforcescale; +float autocvar_g_balance_keyhunt_delay_collect; +float autocvar_g_balance_keyhunt_delay_damage_return; +float autocvar_g_balance_keyhunt_delay_return; +float autocvar_g_balance_keyhunt_delay_round; +float autocvar_g_balance_keyhunt_delay_tracking; +float autocvar_g_balance_keyhunt_return_when_unreachable; +float autocvar_g_balance_keyhunt_dropvelocity; +float autocvar_g_balance_keyhunt_maxdist; +float autocvar_g_balance_keyhunt_protecttime; + +int autocvar_g_balance_keyhunt_score_capture; +int autocvar_g_balance_keyhunt_score_carrierfrag; +int autocvar_g_balance_keyhunt_score_collect; +int autocvar_g_balance_keyhunt_score_destroyed; +int autocvar_g_balance_keyhunt_score_destroyed_ownfactor; +int autocvar_g_balance_keyhunt_score_push; +float autocvar_g_balance_keyhunt_throwvelocity; + +//int autocvar_g_keyhunt_teams; +int autocvar_g_keyhunt_teams_override; + +// #define KH_PLAYER_USE_ATTACHMENT +// #define KH_PLAYER_USE_CARRIEDMODEL + +#ifdef KH_PLAYER_USE_ATTACHMENT +const vector KH_PLAYER_ATTACHMENT_DIST_ROTATED = '0 -4 0'; +const vector KH_PLAYER_ATTACHMENT_DIST = '4 0 0'; +const vector KH_PLAYER_ATTACHMENT = '0 0 0'; +const vector KH_PLAYER_ATTACHMENT_ANGLES = '0 0 0'; +const string KH_PLAYER_ATTACHMENT_BONE = ""; +#else +const float KH_KEY_ZSHIFT = 22; +const float KH_KEY_XYDIST = 24; +const float KH_KEY_XYSPEED = 45; +#endif +const float KH_KEY_WP_ZSHIFT = 20; + +const vector KH_KEY_MIN = '-10 -10 -46'; +const vector KH_KEY_MAX = '10 10 3'; +const float KH_KEY_BRIGHTNESS = 2; + +bool kh_no_radar_circles; + +// kh_state +// bits 0- 4: team of key 1, or 0 for no such key, or 30 for dropped, or 31 for self +// bits 5- 9: team of key 2, or 0 for no such key, or 30 for dropped, or 31 for self +// bits 10-14: team of key 3, or 0 for no such key, or 30 for dropped, or 31 for self +// bits 15-19: team of key 4, or 0 for no such key, or 30 for dropped, or 31 for self +.float siren_time; // time delay the siren +//.float stuff_time; // time delay to stuffcmd a cvar + +int kh_keystatus[17]; +//kh_keystatus[0] = status of dropped keys, kh_keystatus[1 - 16] = player # +//replace 17 with cvar("maxplayers") or similar !!!!!!!!! +//for(i = 0; i < maxplayers; ++i) +// kh_keystatus[i] = "0"; + +int kh_Team_ByID(int t) +{ + if(t == 0) return NUM_TEAM_1; + if(t == 1) return NUM_TEAM_2; + if(t == 2) return NUM_TEAM_3; + if(t == 3) return NUM_TEAM_4; + return 0; +} + +//entity kh_worldkeylist; +.entity kh_worldkeynext; +entity kh_controller; +//bool kh_tracking_enabled; +int kh_teams; +int kh_interferemsg_team; +float kh_interferemsg_time; +.entity kh_next, kh_prev; // linked list +.float kh_droptime; +.int kh_dropperteam; +.entity kh_previous_owner; +.int kh_previous_owner_playerid; + +int kh_key_dropped, kh_key_carried; + +int kh_Key_AllOwnedByWhichTeam(); + +const int ST_KH_CAPS = 1; +void kh_ScoreRules(int teams) +{ + GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, { + field_team(ST_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); + field(SP_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); + field(SP_KH_PUSHES, "pushes", 0); + field(SP_KH_DESTROYS, "destroyed", SFL_LOWER_IS_BETTER); + field(SP_KH_PICKUPS, "pickups", 0); + field(SP_KH_KCKILLS, "kckills", 0); + field(SP_KH_LOSSES, "losses", SFL_LOWER_IS_BETTER); + }); +} + +bool kh_KeyCarrier_waypointsprite_visible_for_player(entity this, entity player, entity view) // runs all the time +{ + if(!IS_PLAYER(view) || DIFF_TEAM(this, view)) + if(!kh_tracking_enabled) + return false; + + return true; +} + +bool kh_Key_waypointsprite_visible_for_player(entity this, entity player, entity view) +{ + if(!kh_tracking_enabled) + return false; + if(!this.owner) + return true; + if(!this.owner.owner) + return true; + return false; // draw only when key is not owned +} + +void kh_update_state() +{ + entity key; + int f; + int s = 0; + FOR_EACH_KH_KEY(key) + { + if(key.owner) + f = key.team; + else + f = 30; + s |= (32 ** key.count) * f; + } + + FOREACH_CLIENT(true, { STAT(KH_KEYS, it) = s; }); + + FOR_EACH_KH_KEY(key) + { + if(key.owner) + STAT(KH_KEYS, key.owner) |= (32 ** key.count) * 31; + } + //print(ftos((nextent(NULL)).kh_state), "\n"); +} + + + + +var kh_Think_t kh_Controller_Thinkfunc; +void kh_Controller_SetThink(float t, kh_Think_t func) // runs occasionaly +{ + kh_Controller_Thinkfunc = func; + kh_controller.cnt = ceil(t); + if(t == 0) + kh_controller.nextthink = time; // force +} +void kh_WaitForPlayers(); +void kh_Controller_Think(entity this) // called a lot +{ + if(game_stopped) + return; + if(this.cnt > 0) + { + if(getthink(this) != kh_WaitForPlayers) + this.cnt -= 1; + } + else if(this.cnt == 0) + { + this.cnt -= 1; + kh_Controller_Thinkfunc(); + } + this.nextthink = time + 1; +} + +// frags f: take from cvar * f +// frags 0: no frags +void kh_Scores_Event(entity player, entity key, string what, float frags_player, float frags_owner) // update the score when a key is captured +{ + string s; + if(game_stopped) + return; + + if(frags_player) + UpdateFrags(player, frags_player); + + if(key && key.owner && frags_owner) + UpdateFrags(key.owner, frags_owner); + + if(!autocvar_sv_eventlog) //output extra info to the console or text file + return; + + s = strcat(":keyhunt:", what, ":", ftos(player.playerid), ":", ftos(frags_player)); + + if(key && key.owner) + s = strcat(s, ":", ftos(key.owner.playerid)); + else + s = strcat(s, ":0"); + + s = strcat(s, ":", ftos(frags_owner), ":"); + + if(key) + s = strcat(s, key.netname); + + GameLogEcho(s); +} + +vector kh_AttachedOrigin(entity e) // runs when a team captures the flag, it can run 2 or 3 times. +{ + if(e.tag_entity) + { + makevectors(e.tag_entity.angles); + return e.tag_entity.origin + e.origin.x * v_forward - e.origin.y * v_right + e.origin.z * v_up; + } + else + return e.origin; +} + +void kh_Key_Attach(entity key) // runs when a player picks up a key and several times when a key is assigned to a player at the start of a round +{ +#ifdef KH_PLAYER_USE_ATTACHMENT + entity first = key.owner.kh_next; + if(key == first) + { + setattachment(key, key.owner, KH_PLAYER_ATTACHMENT_BONE); + if(key.kh_next) + { + setattachment(key.kh_next, key, ""); + setorigin(key, key.kh_next.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST); + setorigin(key.kh_next, KH_PLAYER_ATTACHMENT_DIST_ROTATED); + key.kh_next.angles = '0 0 0'; + } + else + setorigin(key, KH_PLAYER_ATTACHMENT); + key.angles = KH_PLAYER_ATTACHMENT_ANGLES; + } + else + { + setattachment(key, key.kh_prev, ""); + if(key.kh_next) + setattachment(key.kh_next, key, ""); + setorigin(key, KH_PLAYER_ATTACHMENT_DIST_ROTATED); + setorigin(first, first.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST); + key.angles = '0 0 0'; + } +#else + setattachment(key, key.owner, ""); + setorigin(key, '0 0 1' * KH_KEY_ZSHIFT); // fixing x, y in think + key.angles_y -= key.owner.angles.y; +#endif + key.flags = 0; + if(IL_CONTAINS(g_items, key)) + IL_REMOVE(g_items, key); + key.solid = SOLID_NOT; + set_movetype(key, MOVETYPE_NONE); + key.team = key.owner.team; + key.nextthink = time; + key.damageforcescale = 0; + key.takedamage = DAMAGE_NO; + key.modelindex = kh_key_carried; + navigation_dynamicgoal_unset(key); +} + +void kh_Key_Detach(entity key) // runs every time a key is dropped or lost. Runs several times times when all the keys are captured +{ +#ifdef KH_PLAYER_USE_ATTACHMENT + entity first = key.owner.kh_next; + if(key == first) + { + if(key.kh_next) + { + setattachment(key.kh_next, key.owner, KH_PLAYER_ATTACHMENT_BONE); + setorigin(key.kh_next, key.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST); + key.kh_next.angles = KH_PLAYER_ATTACHMENT_ANGLES; + } + } + else + { + if(key.kh_next) + setattachment(key.kh_next, key.kh_prev, ""); + setorigin(first, first.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST); + } + // in any case: + setattachment(key, NULL, ""); + setorigin(key, key.owner.origin + '0 0 1' * (STAT(PL_MIN, key.owner).z - KH_KEY_MIN_z)); + key.angles = key.owner.angles; +#else + setorigin(key, key.owner.origin + key.origin.z * '0 0 1'); + setattachment(key, NULL, ""); + key.angles_y += key.owner.angles.y; +#endif + key.flags = FL_ITEM; + if(!IL_CONTAINS(g_items, key)) + IL_PUSH(g_items, key); + key.solid = SOLID_TRIGGER; + set_movetype(key, MOVETYPE_TOSS); + key.pain_finished = time + autocvar_g_balance_keyhunt_delay_return; + key.damageforcescale = autocvar_g_balance_keyhunt_damageforcescale; + key.takedamage = DAMAGE_YES; + // let key.team stay + key.modelindex = kh_key_dropped; + navigation_dynamicgoal_set(key); + key.kh_previous_owner = key.owner; + key.kh_previous_owner_playerid = key.owner.playerid; +} + +void kh_Key_AssignTo(entity key, entity player) // runs every time a key is picked up or assigned. Runs prior to kh_key_attach +{ + if(key.owner == player) + return; + + int ownerteam0 = kh_Key_AllOwnedByWhichTeam(); + + if(key.owner) + { + kh_Key_Detach(key); + + // remove from linked list + if(key.kh_next) + key.kh_next.kh_prev = key.kh_prev; + key.kh_prev.kh_next = key.kh_next; + key.kh_next = NULL; + key.kh_prev = NULL; + + if(key.owner.kh_next == NULL) + { + // No longer a key carrier + if(!kh_no_radar_circles) + WaypointSprite_Ping(key.owner.waypointsprite_attachedforcarrier); + WaypointSprite_DetachCarrier(key.owner); + } + } + + key.owner = player; + + if(player) + { + // insert into linked list + key.kh_next = player.kh_next; + key.kh_prev = player; + player.kh_next = key; + if(key.kh_next) + key.kh_next.kh_prev = key; + + float i; + i = kh_keystatus[key.owner.playerid]; + if(key.netname == "^1red key") + i += 1; + if(key.netname == "^4blue key") + i += 2; + if(key.netname == "^3yellow key") + i += 4; + if(key.netname == "^6pink key") + i += 8; + kh_keystatus[key.owner.playerid] = i; + + kh_Key_Attach(key); + + if(key.kh_next == NULL) + { + // player is now a key carrier + entity wp = WaypointSprite_AttachCarrier(WP_Null, player, RADARICON_FLAGCARRIER); + wp.colormod = colormapPaletteColor(player.team - 1, 0); + player.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_KeyCarrier_waypointsprite_visible_for_player; + WaypointSprite_UpdateRule(player.waypointsprite_attachedforcarrier, player.team, SPRITERULE_TEAMPLAY); + if(player.team == NUM_TEAM_1) + WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierRed, WP_KeyCarrierFriend, WP_KeyCarrierRed); + else if(player.team == NUM_TEAM_2) + WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierBlue, WP_KeyCarrierFriend, WP_KeyCarrierBlue); + else if(player.team == NUM_TEAM_3) + WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierYellow, WP_KeyCarrierFriend, WP_KeyCarrierYellow); + else if(player.team == NUM_TEAM_4) + WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierPink, WP_KeyCarrierFriend, WP_KeyCarrierPink); + if(!kh_no_radar_circles) + WaypointSprite_Ping(player.waypointsprite_attachedforcarrier); + } + } + + // moved that here, also update if there's no player + kh_update_state(); + + key.pusher = NULL; + + int ownerteam = kh_Key_AllOwnedByWhichTeam(); + if(ownerteam != ownerteam0) + { + entity k; + if(ownerteam != -1) + { + kh_interferemsg_time = time + 0.2; + kh_interferemsg_team = player.team; + + // audit all key carrier sprites, update them to "Run here" + FOR_EACH_KH_KEY(k) + { + if (!k.owner) continue; + entity first = WP_Null; + FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, { first = it; break; }); + entity third = WP_Null; + FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { third = it; break; }); + WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFinish, third); + } + } + else + { + kh_interferemsg_time = 0; + + // audit all key carrier sprites, update them to "Key Carrier" + FOR_EACH_KH_KEY(k) + { + if (!k.owner) continue; + entity first = WP_Null; + FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, { first = it; break; }); + entity third = WP_Null; + FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { third = it; break; }); + WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFriend, third); + } + } + } +} + +void kh_Key_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) +{ + if(this.owner) + return; + if(ITEM_DAMAGE_NEEDKILL(deathtype)) + { + this.pain_finished = bound(time, time + autocvar_g_balance_keyhunt_delay_damage_return, this.pain_finished); + return; + } + if(force == '0 0 0') + return; + if(time > this.pushltime) + if(IS_PLAYER(attacker)) + this.team = attacker.team; +} + +void kh_Key_Collect(entity key, entity player) //a player picks up a dropped key +{ + sound(player, CH_TRIGGER, SND_KH_COLLECT, VOL_BASE, ATTEN_NORM); + + if(key.kh_dropperteam != player.team) + { + kh_Scores_Event(player, key, "collect", autocvar_g_balance_keyhunt_score_collect, 0); + GameRules_scoring_add(player, KH_PICKUPS, 1); + } + key.kh_dropperteam = 0; + int realteam = kh_Team_ByID(key.count); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PICKUP), player.netname); + + kh_Key_AssignTo(key, player); // this also updates .kh_state +} + +void kh_Key_Touch(entity this, entity toucher) // runs many, many times when a key has been dropped and can be picked up +{ + if(game_stopped) + return; + + if(this.owner) // already carried + return; + + if(ITEM_TOUCH_NEEDKILL()) + { + this.pain_finished = bound(time, time + autocvar_g_balance_keyhunt_delay_damage_return, this.pain_finished); + return; + } + + if (!IS_PLAYER(toucher)) + return; + if(IS_DEAD(toucher)) + return; + if(toucher == this.enemy) + if(time < this.kh_droptime + autocvar_g_balance_keyhunt_delay_collect) + return; // you just dropped it! + kh_Key_Collect(this, toucher); +} + +void kh_Key_Remove(entity key) // runs after when all the keys have been collected or when a key has been dropped for more than X seconds +{ + entity o = key.owner; + kh_Key_AssignTo(key, NULL); + if(o) // it was attached + WaypointSprite_Kill(key.waypointsprite_attachedforcarrier); + else // it was dropped + WaypointSprite_DetachCarrier(key); + + // remove key from key list + if (kh_worldkeylist == key) + kh_worldkeylist = kh_worldkeylist.kh_worldkeynext; + else + { + o = kh_worldkeylist; + while (o) + { + if (o.kh_worldkeynext == key) + { + o.kh_worldkeynext = o.kh_worldkeynext.kh_worldkeynext; + break; + } + o = o.kh_worldkeynext; + } + } + + delete(key); + + kh_update_state(); +} + +void kh_FinishRound() // runs when a team captures the keys +{ + // prepare next round + kh_interferemsg_time = 0; + entity key; + + kh_no_radar_circles = true; + FOR_EACH_KH_KEY(key) + kh_Key_Remove(key); + kh_no_radar_circles = false; + + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round); + kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound); +} + +void nades_GiveBonus(entity player, float score); + +void kh_WinnerTeam(int winner_team) // runs when a team wins +{ + // all key carriers get some points + entity key; + float score = (NumTeams(kh_teams) - 1) * autocvar_g_balance_keyhunt_score_capture; + DistributeEvenly_Init(score, NumTeams(kh_teams)); + // twice the score for 3 team games, three times the score for 4 team games! + // note: for a win by destroying the key, this should NOT be applied + FOR_EACH_KH_KEY(key) + { + float f = DistributeEvenly_Get(1); + kh_Scores_Event(key.owner, key, "capture", f, 0); + GameRules_scoring_add_team(key.owner, KH_CAPS, 1); + nades_GiveBonus(key.owner, autocvar_g_nades_bonus_score_high); + } + + bool first = true; + string keyowner = ""; + FOR_EACH_KH_KEY(key) + if(key.owner.kh_next == key) + { + if(!first) + keyowner = strcat(keyowner, ", "); + keyowner = key.owner.netname; + first = false; + } + + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN)); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_KEYHUNT_CAPTURE), keyowner); + + first = true; + vector firstorigin = '0 0 0', lastorigin = '0 0 0', midpoint = '0 0 0'; + FOR_EACH_KH_KEY(key) + { + vector thisorigin = kh_AttachedOrigin(key); + //dprint("Key origin: ", vtos(thisorigin), "\n"); + midpoint += thisorigin; + + if(!first) + te_lightning2(NULL, lastorigin, thisorigin); + lastorigin = thisorigin; + if(first) + firstorigin = thisorigin; + first = false; + } + if(NumTeams(kh_teams) > 2) + { + te_lightning2(NULL, lastorigin, firstorigin); + } + midpoint = midpoint * (1 / NumTeams(kh_teams)); + te_customflash(midpoint, 1000, 1, Team_ColorRGB(winner_team) * 0.5 + '0.5 0.5 0.5'); // make the color >=0.5 in each component + + play2all(SND(KH_CAPTURE)); + kh_FinishRound(); +} + +void kh_LoserTeam(int loser_team, entity lostkey) // runs when a player pushes a flag carrier off the map +{ + float f; + entity attacker = NULL; + if(lostkey.pusher) + if(lostkey.pusher.team != loser_team) + if(IS_PLAYER(lostkey.pusher)) + attacker = lostkey.pusher; + + if(attacker) + { + if(lostkey.kh_previous_owner) + kh_Scores_Event(lostkey.kh_previous_owner, NULL, "pushed", 0, -autocvar_g_balance_keyhunt_score_push); + // don't actually GIVE him the -nn points, just log + kh_Scores_Event(attacker, NULL, "push", autocvar_g_balance_keyhunt_score_push, 0); + GameRules_scoring_add(attacker, KH_PUSHES, 1); + //centerprint(attacker, "Your push is the best!"); // does this really need to exist? + } + else + { + int players = 0; + float of = autocvar_g_balance_keyhunt_score_destroyed_ownfactor; + + FOREACH_CLIENT(IS_PLAYER(it) && it.team != loser_team, { ++players; }); + + entity key; + int keys = 0; + FOR_EACH_KH_KEY(key) + if(key.owner && key.team != loser_team) + ++keys; + + if(lostkey.kh_previous_owner) + kh_Scores_Event(lostkey.kh_previous_owner, NULL, "destroyed", 0, -autocvar_g_balance_keyhunt_score_destroyed); + // don't actually GIVE him the -nn points, just log + + if(lostkey.kh_previous_owner.playerid == lostkey.kh_previous_owner_playerid) + GameRules_scoring_add(lostkey.kh_previous_owner, KH_DESTROYS, 1); + + DistributeEvenly_Init(autocvar_g_balance_keyhunt_score_destroyed, keys * of + players); + + FOR_EACH_KH_KEY(key) + if(key.owner && key.team != loser_team) + { + f = DistributeEvenly_Get(of); + kh_Scores_Event(key.owner, NULL, "destroyed_holdingkey", f, 0); + } + + int fragsleft = DistributeEvenly_Get(players); + + // Now distribute these among all other teams... + int j = NumTeams(kh_teams) - 1; + for(int i = 0; i < NumTeams(kh_teams); ++i) + { + int thisteam = kh_Team_ByID(i); + if(thisteam == loser_team) // bad boy, no cookie - this WILL happen + continue; + + players = 0; + FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, { ++players; }); + + DistributeEvenly_Init(fragsleft, j); + fragsleft = DistributeEvenly_Get(j - 1); + DistributeEvenly_Init(DistributeEvenly_Get(1), players); + + FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, { + f = DistributeEvenly_Get(1); + kh_Scores_Event(it, NULL, "destroyed", f, 0); + }); + + --j; + } + } + + int realteam = kh_Team_ByID(lostkey.count); + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(loser_team, CENTER_ROUND_TEAM_LOSS)); + if(attacker) + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PUSHED), attacker.netname, lostkey.kh_previous_owner.netname); + else + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DESTROYED), lostkey.kh_previous_owner.netname); + + play2all(SND(KH_DESTROY)); + te_tarexplosion(lostkey.origin); + + kh_FinishRound(); +} + +void kh_Key_Think(entity this) // runs all the time +{ + if(game_stopped) + return; + + if(this.owner) + { +#ifndef KH_PLAYER_USE_ATTACHMENT + makevectors('0 1 0' * (this.cnt + (time % 360) * KH_KEY_XYSPEED)); + setorigin(this, v_forward * KH_KEY_XYDIST + '0 0 1' * this.origin.z); +#endif + } + + // if in nodrop or time over, end the round + if(!this.owner) + if(time > this.pain_finished) + kh_LoserTeam(this.team, this); + + if(this.owner) + if(kh_Key_AllOwnedByWhichTeam() != -1) + { + if(this.siren_time < time) + { + sound(this.owner, CH_TRIGGER, SND_KH_ALARM, VOL_BASE, ATTEN_NORM); // play a simple alarm + this.siren_time = time + 2.5; // repeat every 2.5 seconds + } + + entity key; + vector p = this.owner.origin; + FOR_EACH_KH_KEY(key) + if(vdist(key.owner.origin - p, >, autocvar_g_balance_keyhunt_maxdist)) + goto not_winning; + kh_WinnerTeam(this.team); +LABEL(not_winning) + } + + if(kh_interferemsg_time && time > kh_interferemsg_time) + { + kh_interferemsg_time = 0; + FOREACH_CLIENT(IS_PLAYER(it), { + if(it.team == kh_interferemsg_team) + if(it.kh_next) + Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_MEET); + else + Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_HELP); + else + Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_TEAM_NUM(kh_interferemsg_team, CENTER_KEYHUNT_INTERFERE)); + }); + } + + this.nextthink = time + 0.05; +} + +void key_reset(entity this) +{ + kh_Key_AssignTo(this, NULL); + kh_Key_Remove(this); +} + +const string STR_ITEM_KH_KEY = "item_kh_key"; +void kh_Key_Spawn(entity initial_owner, float _angle, float i) // runs every time a new flag is created, ie after all the keys have been collected +{ + entity key = spawn(); + key.count = i; + key.classname = STR_ITEM_KH_KEY; + settouch(key, kh_Key_Touch); + setthink(key, kh_Key_Think); + key.nextthink = time; + key.items = IT_KEY1 | IT_KEY2; + key.cnt = _angle; + key.angles = '0 360 0' * random(); + key.event_damage = kh_Key_Damage; + key.takedamage = DAMAGE_YES; + key.damagedbytriggers = autocvar_g_balance_keyhunt_return_when_unreachable; + key.damagedbycontents = autocvar_g_balance_keyhunt_return_when_unreachable; + key.modelindex = kh_key_dropped; + key.model = "key"; + key.kh_dropperteam = 0; + key.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; + setsize(key, KH_KEY_MIN, KH_KEY_MAX); + key.colormod = Team_ColorRGB(initial_owner.team) * KH_KEY_BRIGHTNESS; + key.reset = key_reset; + navigation_dynamicgoal_init(key, false); + + switch(initial_owner.team) + { + case NUM_TEAM_1: + key.netname = "^1red key"; + break; + case NUM_TEAM_2: + key.netname = "^4blue key"; + break; + case NUM_TEAM_3: + key.netname = "^3yellow key"; + break; + case NUM_TEAM_4: + key.netname = "^6pink key"; + break; + default: + key.netname = "NETGIER key"; + break; + } + + // link into key list + key.kh_worldkeynext = kh_worldkeylist; + kh_worldkeylist = key; + + Send_Notification(NOTIF_ONE, initial_owner, MSG_CENTER, APP_TEAM_NUM(initial_owner.team, CENTER_KEYHUNT_START)); + + WaypointSprite_Spawn(WP_KeyDropped, 0, 0, key, '0 0 1' * KH_KEY_WP_ZSHIFT, NULL, key.team, key, waypointsprite_attachedforcarrier, false, RADARICON_FLAG); + key.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_Key_waypointsprite_visible_for_player; + + kh_Key_AssignTo(key, initial_owner); +} + +// -1 when no team completely owns all keys yet +int kh_Key_AllOwnedByWhichTeam() // constantly called. check to see if all the keys are owned by the same team +{ + entity key; + int teem = -1; + int keys = NumTeams(kh_teams); + FOR_EACH_KH_KEY(key) + { + if(!key.owner) + return -1; + if(teem == -1) + teem = key.team; + else if(teem != key.team) + return -1; + --keys; + } + if(keys != 0) + return -1; + return teem; +} + +void kh_Key_DropOne(entity key) +{ + // prevent collecting this one for some time + entity player = key.owner; + + key.kh_droptime = time; + key.enemy = player; + + kh_Scores_Event(player, key, "dropkey", 0, 0); + GameRules_scoring_add(player, KH_LOSSES, 1); + int realteam = kh_Team_ByID(key.count); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DROP), player.netname); + + kh_Key_AssignTo(key, NULL); + makevectors(player.v_angle); + key.velocity = W_CalculateProjectileVelocity(player, player.velocity, autocvar_g_balance_keyhunt_throwvelocity * v_forward, false); + key.pusher = NULL; + key.pushltime = time + autocvar_g_balance_keyhunt_protecttime; + key.kh_dropperteam = key.team; + + sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM); +} + +void kh_Key_DropAll(entity player, float suicide) // runs whenever a player dies +{ + if(player.kh_next) + { + entity mypusher = NULL; + if(player.pusher) + if(time < player.pushltime) + mypusher = player.pusher; + + entity key; + while((key = player.kh_next)) + { + kh_Scores_Event(player, key, "losekey", 0, 0); + GameRules_scoring_add(player, KH_LOSSES, 1); + int realteam = kh_Team_ByID(key.count); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_LOST), player.netname); + kh_Key_AssignTo(key, NULL); + makevectors('-1 0 0' * (45 + 45 * random()) + '0 360 0' * random()); + key.velocity = W_CalculateProjectileVelocity(player, player.velocity, autocvar_g_balance_keyhunt_dropvelocity * v_forward, false); + key.pusher = mypusher; + key.pushltime = time + autocvar_g_balance_keyhunt_protecttime; + if(suicide) + key.kh_dropperteam = player.team; + } + sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM); + } +} + +int kh_GetMissingTeams() +{ + int missing_teams = 0; + for(int i = 0; i < NumTeams(kh_teams); ++i) + { + int teem = kh_Team_ByID(i); + int players = 0; + FOREACH_CLIENT(IS_PLAYER(it), { + if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem) + ++players; + }); + if (!players) + missing_teams |= (2 ** i); + } + return missing_teams; +} + +void kh_WaitForPlayers() // delay start of the round until enough players are present +{ + static int prev_missing_teams_mask; + if(time < game_starttime) + { + if (prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers); + return; + } + + int missing_teams_mask = kh_GetMissingTeams(); + if(!missing_teams_mask) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round); + kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound); + } + else + { + if(player_count == 0) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + } + else + { + if(prev_missing_teams_mask != missing_teams_mask) + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask); + prev_missing_teams_mask = missing_teams_mask; + } + } + kh_Controller_SetThink(1, kh_WaitForPlayers); + } +} + +void kh_EnableTrackingDevice() // runs after each round +{ + Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT); + Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER); + + kh_tracking_enabled = true; +} + +void kh_StartRound() // runs at the start of each round +{ + if(time < game_starttime) + { + kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers); + return; + } + + if(kh_GetMissingTeams()) + { + kh_Controller_SetThink(1, kh_WaitForPlayers); + return; + } + + Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT); + Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER); + + for(int i = 0; i < NumTeams(kh_teams); ++i) + { + int teem = kh_Team_ByID(i); + int players = 0; + entity my_player = NULL; + FOREACH_CLIENT(IS_PLAYER(it), { + if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem) + { + ++players; + if(random() * players <= 1) + my_player = it; + } + }); + kh_Key_Spawn(my_player, 360 * i / NumTeams(kh_teams), i); + } + + kh_tracking_enabled = false; + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_SCAN, autocvar_g_balance_keyhunt_delay_tracking); + kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_tracking, kh_EnableTrackingDevice); +} + +float kh_HandleFrags(entity attacker, entity targ, float f) // adds to the player score +{ + if(attacker == targ) + return f; + + if(targ.kh_next) + { + if(attacker.team == targ.team) + { + int nk = 0; + for(entity k = targ.kh_next; k != NULL; k = k.kh_next) + ++nk; + kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", -nk * autocvar_g_balance_keyhunt_score_collect, 0); + } + else + { + kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", autocvar_g_balance_keyhunt_score_carrierfrag-1, 0); + GameRules_scoring_add(attacker, KH_KCKILLS, 1); + // the frag gets added later + } + } + + return f; +} + +void kh_Initialize() // sets up th KH environment +{ + // setup variables + kh_teams = autocvar_g_keyhunt_teams_override; + if(kh_teams < 2) + kh_teams = cvar("g_keyhunt_teams"); // read the cvar directly as it gets written earlier in the same frame + kh_teams = BITS(bound(2, kh_teams, 4)); + + // make a KH entity for controlling the game + kh_controller = spawn(); + setthink(kh_controller, kh_Controller_Think); + kh_Controller_SetThink(0, kh_WaitForPlayers); + + setmodel(kh_controller, MDL_KH_KEY); + kh_key_dropped = kh_controller.modelindex; + /* + dprint(vtos(kh_controller.mins)); + dprint(vtos(kh_controller.maxs)); + dprint("\n"); + */ +#ifdef KH_PLAYER_USE_CARRIEDMODEL + setmodel(kh_controller, MDL_KH_KEY_CARRIED); + kh_key_carried = kh_controller.modelindex; +#else + kh_key_carried = kh_key_dropped; +#endif + + kh_controller.model = ""; + kh_controller.modelindex = 0; + + kh_ScoreRules(kh_teams); +} + +void kh_finalize() +{ + // to be called before intermission + kh_FinishRound(); + delete(kh_controller); + kh_controller = NULL; +} + +// legacy bot role + +void(entity this) havocbot_role_kh_carrier; +void(entity this) havocbot_role_kh_defense; +void(entity this) havocbot_role_kh_offense; +void(entity this) havocbot_role_kh_freelancer; + + +void havocbot_goalrating_kh(entity this, float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy) +{ + entity head; + for (head = kh_worldkeylist; head; head = head.kh_worldkeynext) + { + if(head.owner == this) + continue; + if(!kh_tracking_enabled) + { + // if it's carried by our team we know about it + // otherwise we have to see it to know about it + if(!head.owner || head.team != this.team) + { + traceline(this.origin + this.view_ofs, head.origin, MOVE_NOMONSTERS, this); + if (trace_fraction < 1 && trace_ent != head) + continue; // skip what I can't see + } + } + if(!head.owner) + navigation_routerating(this, head, ratingscale_dropped * 10000, 100000); + else if(head.team == this.team) + navigation_routerating(this, head.owner, ratingscale_team * 10000, 100000); + else + navigation_routerating(this, head.owner, ratingscale_enemy * 10000, 100000); + } + + havocbot_goalrating_items(this, 1, this.origin, 10000); +} + +void havocbot_role_kh_carrier(entity this) +{ + if(IS_DEAD(this)) + return; + + if (!(this.kh_next)) + { + LOG_TRACE("changing role to freelancer"); + this.havocbot_role = havocbot_role_kh_freelancer; + this.havocbot_role_timeout = 0; + return; + } + + if (navigation_goalrating_timeout(this)) + { + navigation_goalrating_start(this); + + if(kh_Key_AllOwnedByWhichTeam() == this.team) + havocbot_goalrating_kh(this, 10, 0.1, 0.1); // bring home + else + havocbot_goalrating_kh(this, 4, 4, 1); // play defensively + + navigation_goalrating_end(this); + + navigation_goalrating_timeout_set(this); + } +} + +void havocbot_role_kh_defense(entity this) +{ + if(IS_DEAD(this)) + return; + + if (this.kh_next) + { + LOG_TRACE("changing role to carrier"); + this.havocbot_role = havocbot_role_kh_carrier; + this.havocbot_role_timeout = 0; + return; + } + + if (!this.havocbot_role_timeout) + this.havocbot_role_timeout = time + random() * 10 + 20; + if (time > this.havocbot_role_timeout) + { + LOG_TRACE("changing role to freelancer"); + this.havocbot_role = havocbot_role_kh_freelancer; + this.havocbot_role_timeout = 0; + return; + } + + if (navigation_goalrating_timeout(this)) + { + float key_owner_team; + navigation_goalrating_start(this); + + key_owner_team = kh_Key_AllOwnedByWhichTeam(); + if(key_owner_team == this.team) + havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend key carriers + else if(key_owner_team == -1) + havocbot_goalrating_kh(this, 4, 1, 0.1); // play defensively + else + havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY + + navigation_goalrating_end(this); + + navigation_goalrating_timeout_set(this); + } +} + +void havocbot_role_kh_offense(entity this) +{ + if(IS_DEAD(this)) + return; + + if (this.kh_next) + { + LOG_TRACE("changing role to carrier"); + this.havocbot_role = havocbot_role_kh_carrier; + this.havocbot_role_timeout = 0; + return; + } + + if (!this.havocbot_role_timeout) + this.havocbot_role_timeout = time + random() * 10 + 20; + if (time > this.havocbot_role_timeout) + { + LOG_TRACE("changing role to freelancer"); + this.havocbot_role = havocbot_role_kh_freelancer; + this.havocbot_role_timeout = 0; + return; + } + + if (navigation_goalrating_timeout(this)) + { + float key_owner_team; + + navigation_goalrating_start(this); + + key_owner_team = kh_Key_AllOwnedByWhichTeam(); + if(key_owner_team == this.team) + havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway + else if(key_owner_team == -1) + havocbot_goalrating_kh(this, 0.1, 1, 4); // play offensively + else + havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK! EMERGENCY! + + navigation_goalrating_end(this); + + navigation_goalrating_timeout_set(this); + } +} + +void havocbot_role_kh_freelancer(entity this) +{ + if(IS_DEAD(this)) + return; + + if (this.kh_next) + { + LOG_TRACE("changing role to carrier"); + this.havocbot_role = havocbot_role_kh_carrier; + this.havocbot_role_timeout = 0; + return; + } + + if (!this.havocbot_role_timeout) + this.havocbot_role_timeout = time + random() * 10 + 10; + if (time > this.havocbot_role_timeout) + { + if (random() < 0.5) + { + LOG_TRACE("changing role to offense"); + this.havocbot_role = havocbot_role_kh_offense; + } + else + { + LOG_TRACE("changing role to defense"); + this.havocbot_role = havocbot_role_kh_defense; + } + this.havocbot_role_timeout = 0; + return; + } + + if (navigation_goalrating_timeout(this)) + { + navigation_goalrating_start(this); + + int key_owner_team = kh_Key_AllOwnedByWhichTeam(); + if(key_owner_team == this.team) + havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway + else if(key_owner_team == -1) + havocbot_goalrating_kh(this, 1, 10, 4); // prefer dropped keys + else + havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY + + navigation_goalrating_end(this); + + navigation_goalrating_timeout_set(this); + } +} + + +// register this as a mutator + +MUTATOR_HOOKFUNCTION(kh, ClientDisconnect) +{ + entity player = M_ARGV(0, entity); + + kh_Key_DropAll(player, true); +} + +MUTATOR_HOOKFUNCTION(kh, MakePlayerObserver) +{ + entity player = M_ARGV(0, entity); + + kh_Key_DropAll(player, true); +} + +MUTATOR_HOOKFUNCTION(kh, PlayerDies) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + + if(frag_target == frag_attacker) + kh_Key_DropAll(frag_target, true); + else if(IS_PLAYER(frag_attacker)) + kh_Key_DropAll(frag_target, false); + else + kh_Key_DropAll(frag_target, true); +} + +MUTATOR_HOOKFUNCTION(kh, GiveFragsForKill, CBC_ORDER_FIRST) +{ + entity frag_attacker = M_ARGV(0, entity); + entity frag_target = M_ARGV(1, entity); + float frag_score = M_ARGV(2, float); + M_ARGV(2, float) = kh_HandleFrags(frag_attacker, frag_target, frag_score); +} + +MUTATOR_HOOKFUNCTION(kh, MatchEnd) +{ + kh_finalize(); +} + +MUTATOR_HOOKFUNCTION(kh, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE) +{ + M_ARGV(0, float) = kh_teams; +} + +MUTATOR_HOOKFUNCTION(kh, SpectateCopy) +{ + entity spectatee = M_ARGV(0, entity); + entity client = M_ARGV(1, entity); + + STAT(KH_KEYS, client) = STAT(KH_KEYS, spectatee); +} + +MUTATOR_HOOKFUNCTION(kh, PlayerUseKey) +{ + entity player = M_ARGV(0, entity); + + if(MUTATOR_RETURNVALUE == 0) + { + entity k = player.kh_next; + if(k) + { + kh_Key_DropOne(k); + return true; + } + } +} + +MUTATOR_HOOKFUNCTION(kh, HavocBot_ChooseRole) +{ + entity bot = M_ARGV(0, entity); + + if(IS_DEAD(bot)) + return true; + + float r = random() * 3; + if (r < 1) + bot.havocbot_role = havocbot_role_kh_offense; + else if (r < 2) + bot.havocbot_role = havocbot_role_kh_defense; + else + bot.havocbot_role = havocbot_role_kh_freelancer; + + return true; +} + +MUTATOR_HOOKFUNCTION(kh, DropSpecialItems) +{ + entity frag_target = M_ARGV(0, entity); + + kh_Key_DropAll(frag_target, false); +} + +MUTATOR_HOOKFUNCTION(kh, reset_map_global) +{ + kh_WaitForPlayers(); // takes care of killing the "missing teams" message +} +#endif diff --git a/qcsrc/common/gamemodes/gamemode/keyhunt/keyhunt.qh b/qcsrc/common/gamemodes/gamemode/keyhunt/keyhunt.qh new file mode 100644 index 000000000..a086ee689 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/keyhunt/keyhunt.qh @@ -0,0 +1,36 @@ +#pragma once + +#ifdef SVQC +#include +#define autocvar_g_keyhunt_point_limit cvar("g_keyhunt_point_limit") +int autocvar_g_keyhunt_point_leadlimit; +bool autocvar_g_keyhunt_team_spawns; +void kh_Initialize(); + +REGISTER_MUTATOR(kh, false) +{ + MUTATOR_STATIC(); + MUTATOR_ONADD + { + GameRules_teams(true); + GameRules_spawning_teams(autocvar_g_keyhunt_team_spawns); + GameRules_limit_score(autocvar_g_keyhunt_point_limit); + GameRules_limit_lead(autocvar_g_keyhunt_point_leadlimit); + + kh_Initialize(); + } + return 0; +} + +#define FOR_EACH_KH_KEY(v) for(v = kh_worldkeylist; v; v = v.kh_worldkeynext ) + +// ALL OF THESE should be removed in the future, as other code should not have to care + +// used by bots: +bool kh_tracking_enabled; +.entity kh_next; + +USING(kh_Think_t, void()); +void kh_StartRound(); +void kh_Controller_SetThink(float t, kh_Think_t func); +#endif diff --git a/qcsrc/common/gamemodes/gamemode/lms/_mod.inc b/qcsrc/common/gamemodes/gamemode/lms/_mod.inc new file mode 100644 index 000000000..43bb76d7f --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/lms/_mod.inc @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/lms/_mod.qh b/qcsrc/common/gamemodes/gamemode/lms/_mod.qh new file mode 100644 index 000000000..5e780bb67 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/lms/_mod.qh @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/lms/lms.qc b/qcsrc/common/gamemodes/gamemode/lms/lms.qc new file mode 100644 index 000000000..5e97248c6 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/lms/lms.qc @@ -0,0 +1,431 @@ +#include "lms.qh" + +#ifdef SVQC +#include +#include +#include + +int autocvar_g_lms_extra_lives; +bool autocvar_g_lms_join_anytime; +int autocvar_g_lms_last_join; +bool autocvar_g_lms_regenerate; + +// main functions +float LMS_NewPlayerLives() +{ + float fl; + fl = autocvar_fraglimit; + if(fl == 0) + fl = 999; + + // first player has left the game for dying too much? Nobody else can get in. + if(lms_lowest_lives < 1) + return 0; + + if(!autocvar_g_lms_join_anytime) + if(lms_lowest_lives < fl - autocvar_g_lms_last_join) + return 0; + + return bound(1, lms_lowest_lives, fl); +} + +void ClearWinners(); + +// LMS winning condition: game terminates if and only if there's at most one +// one player who's living lives. Top two scores being equal cancels the time +// limit. +int WinningCondition_LMS() +{ + entity first_player = NULL; + int total_players = 0; + FOREACH_CLIENT(IS_PLAYER(it), { + if (!total_players) + first_player = it; + ++total_players; + }); + + if (total_players) + { + if (total_players > 1) + { + // two or more active players - continue with the game + + if (autocvar_g_campaign) + { + FOREACH_CLIENT(IS_REAL_CLIENT(it), { + float pl_lives = GameRules_scoring_add(it, LMS_LIVES, 0); + if (!pl_lives) + return WINNING_YES; // human player lost, game over + break; + }); + } + } + else + { + // exactly one player? + + ClearWinners(); + SetWinners(winning, 0); // NOTE: exactly one player is still "player", so this works out + + if (LMS_NewPlayerLives()) + { + // game still running (that is, nobody got removed from the game by a frag yet)? then continue + return WINNING_NO; + } + else + { + // a winner! + // and assign him his first place + GameRules_scoring_add(first_player, LMS_RANK, 1); + if(warmup_stage) + return WINNING_NO; + else + return WINNING_YES; + } + } + } + else + { + // nobody is playing at all... + if (LMS_NewPlayerLives()) + { + // wait for players... + } + else + { + // SNAFU (maybe a draw game?) + ClearWinners(); + LOG_TRACE("No players, ending game."); + return WINNING_YES; + } + } + + // When we get here, we have at least two players who are actually LIVING, + // now check if the top two players have equal score. + WinningConditionHelper(NULL); + + ClearWinners(); + if(WinningConditionHelper_winner) + WinningConditionHelper_winner.winning = true; + if(WinningConditionHelper_topscore == WinningConditionHelper_secondscore) + return WINNING_NEVER; + + // Top two have different scores? Way to go for our beloved TIMELIMIT! + return WINNING_NO; +} + +// mutator hooks +MUTATOR_HOOKFUNCTION(lms, reset_map_global) +{ + lms_lowest_lives = 999; +} + +MUTATOR_HOOKFUNCTION(lms, reset_map_players) +{ + FOREACH_CLIENT(true, { + TRANSMUTE(Player, it); + it.frags = FRAGS_PLAYER; + GameRules_scoring_add(it, LMS_LIVES, LMS_NewPlayerLives()); + PutClientInServer(it); + }); +} + +MUTATOR_HOOKFUNCTION(lms, PutClientInServer) +{ + entity player = M_ARGV(0, entity); + + if(player.frags == FRAGS_SPECTATOR) + TRANSMUTE(Observer, player); + else + { + float tl = GameRules_scoring_add(player, LMS_LIVES, 0); + if(tl < lms_lowest_lives) + lms_lowest_lives = tl; + if(tl <= 0) + TRANSMUTE(Observer, player); + if(warmup_stage) + GameRules_scoring_add(player, LMS_RANK, -GameRules_scoring_add(player, LMS_RANK, 0)); + } +} + +MUTATOR_HOOKFUNCTION(lms, ForbidSpawn) +{ + entity player = M_ARGV(0, entity); + + if(warmup_stage) + return false; + if(player.frags == FRAGS_SPECTATOR) + return true; + if(GameRules_scoring_add(player, LMS_LIVES, 0) <= 0) + { + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_LMS_NOLIVES); + return true; + } + return false; +} + +MUTATOR_HOOKFUNCTION(lms, PlayerDies) +{ + entity frag_target = M_ARGV(2, entity); + + frag_target.respawn_flags |= RESPAWN_FORCE; +} + +void lms_RemovePlayer(entity player) +{ + static int quitters = 0; + float player_rank = GameRules_scoring_add(player, LMS_RANK, 0); + if (!player_rank) + { + int pl_cnt = 0; + FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; }); + if (player.lms_spectate_warning != 2) + { + if(IS_BOT_CLIENT(player)) + bot_clear(player); + player.frags = FRAGS_LMS_LOSER; + GameRules_scoring_add(player, LMS_RANK, pl_cnt + 1); + } + else + { + lms_lowest_lives = 999; + FOREACH_CLIENT(true, { + if (it.frags == FRAGS_LMS_LOSER) + { + float it_rank = GameRules_scoring_add(it, LMS_RANK, 0); + if (it_rank > player_rank && it_rank <= 256) + GameRules_scoring_add(it, LMS_RANK, -1); + lms_lowest_lives = 0; + } + else if (it.frags != FRAGS_SPECTATOR) + { + float tl = GameRules_scoring_add(it, LMS_LIVES, 0); + if(tl < lms_lowest_lives) + lms_lowest_lives = tl; + } + }); + GameRules_scoring_add(player, LMS_RANK, 665 - quitters); // different from 666 + if(!warmup_stage) + { + GameRules_scoring_add(player, LMS_LIVES, -GameRules_scoring_add(player, LMS_LIVES, 0)); + ++quitters; + } + player.frags = FRAGS_LMS_LOSER; + TRANSMUTE(Observer, player); + } + if (pl_cnt == 2 && !warmup_stage) // a player is forfeiting leaving only one player + lms_lowest_lives = 0; // end the game now! + } + + if(CS(player).killcount != FRAGS_SPECTATOR) + if(GameRules_scoring_add(player, LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2) + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_NOLIVES, player.netname); + else + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_FORFEIT, player.netname); +} + +MUTATOR_HOOKFUNCTION(lms, ClientDisconnect) +{ + entity player = M_ARGV(0, entity); + + lms_RemovePlayer(player); +} + +MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver) +{ + entity player = M_ARGV(0, entity); + + lms_RemovePlayer(player); + return true; // prevent team reset +} + +MUTATOR_HOOKFUNCTION(lms, ClientConnect) +{ + entity player = M_ARGV(0, entity); + + TRANSMUTE(Player, player); + campaign_bots_may_start = true; + + if(GameRules_scoring_add(player, LMS_LIVES, LMS_NewPlayerLives()) <= 0) + { + GameRules_scoring_add(player, LMS_RANK, 666); // mark as forced spectator for the hud code + player.frags = FRAGS_SPECTATOR; + } +} + +MUTATOR_HOOKFUNCTION(lms, PlayerPreThink) +{ + entity player = M_ARGV(0, entity); + + if(player.deadflag == DEAD_DYING) + player.deadflag = DEAD_RESPAWNING; +} + +MUTATOR_HOOKFUNCTION(lms, PlayerRegen) +{ + if(autocvar_g_lms_regenerate) + return false; + return true; +} + +MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon) +{ + // forbode! + return true; +} + +MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill) +{ + entity frag_target = M_ARGV(1, entity); + + if (!warmup_stage) + { + // remove a life + int tl = GameRules_scoring_add(frag_target, LMS_LIVES, -1); + if(tl < lms_lowest_lives) + lms_lowest_lives = tl; + if(tl <= 0) + { + int pl_cnt = 0; + FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; }); + if(IS_BOT_CLIENT(frag_target)) + bot_clear(frag_target); + frag_target.frags = FRAGS_LMS_LOSER; + GameRules_scoring_add(frag_target, LMS_RANK, pl_cnt); + } + } + M_ARGV(2, float) = 0; // frag score + + return true; +} + +MUTATOR_HOOKFUNCTION(lms, SetStartItems) +{ + start_items &= ~IT_UNLIMITED_AMMO; + start_health = warmup_start_health = cvar("g_lms_start_health"); + start_armorvalue = warmup_start_armorvalue = cvar("g_lms_start_armor"); + start_ammo_shells = warmup_start_ammo_shells = cvar("g_lms_start_ammo_shells"); + start_ammo_nails = warmup_start_ammo_nails = cvar("g_lms_start_ammo_nails"); + start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets"); + start_ammo_cells = warmup_start_ammo_cells = cvar("g_lms_start_ammo_cells"); + start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_lms_start_ammo_plasma"); + start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_lms_start_ammo_fuel"); +} + +MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear) +{ + // don't clear player score + return true; +} + +MUTATOR_HOOKFUNCTION(lms, FilterItemDefinition) +{ + entity definition = M_ARGV(0, entity); + + if (autocvar_g_lms_extra_lives && definition == ITEM_ExtraLife) + { + return false; + } + return true; +} + +void lms_extralife(entity this) +{ + StartItem(this, ITEM_ExtraLife); +} + +MUTATOR_HOOKFUNCTION(lms, OnEntityPreSpawn) +{ + if (!autocvar_g_powerups) return false; + if (!autocvar_g_lms_extra_lives) return false; + + entity ent = M_ARGV(0, entity); + + // Can't use .itemdef here + if (ent.classname != "item_health_mega") return false; + + entity e = spawn(); + setthink(e, lms_extralife); + + e.nextthink = time + 0.1; + e.spawnflags = ent.spawnflags; + e.noalign = ent.noalign; + setorigin(e, ent.origin); + + return true; +} + +MUTATOR_HOOKFUNCTION(lms, ItemTouch) +{ + entity item = M_ARGV(0, entity); + entity toucher = M_ARGV(1, entity); + + if(item.itemdef == ITEM_ExtraLife) + { + Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES); + GameRules_scoring_add(toucher, LMS_LIVES, autocvar_g_lms_extra_lives); + return MUT_ITEMTOUCH_PICKUP; + } + + return MUT_ITEMTOUCH_CONTINUE; +} + +MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE) +{ + FOREACH_CLIENT(IS_REAL_CLIENT(it), { + ++M_ARGV(0, int); // activerealplayers + ++M_ARGV(1, int); // realplayers + }); + + return true; +} + +MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate) +{ + entity player = M_ARGV(0, entity); + + if(warmup_stage || player.lms_spectate_warning) + { + // for the forfeit message... + player.lms_spectate_warning = 2; + } + else + { + if(player.frags != FRAGS_SPECTATOR && player.frags != FRAGS_LMS_LOSER) + { + player.lms_spectate_warning = 1; + sprint(player, "WARNING: you won't be able to enter the game again after spectating in LMS. Use the same command again to spectate anyway.\n"); + } + return MUT_SPECCMD_RETURN; + } + return MUT_SPECCMD_CONTINUE; +} + +MUTATOR_HOOKFUNCTION(lms, CheckRules_World) +{ + M_ARGV(0, float) = WinningCondition_LMS(); + return true; +} + +MUTATOR_HOOKFUNCTION(lms, WantWeapon) +{ + M_ARGV(2, bool) = true; // all weapons +} + +MUTATOR_HOOKFUNCTION(lms, GetPlayerStatus) +{ + return true; +} + +MUTATOR_HOOKFUNCTION(lms, AddPlayerScore) +{ + if(game_stopped) + if(M_ARGV(0, entity) == SP_LMS_RANK) // score field + return true; // allow writing to this field in intermission as it is needed for newly joining players +} + +void lms_Initialize() +{ + lms_lowest_lives = 9999; +} +#endif diff --git a/qcsrc/common/gamemodes/gamemode/lms/lms.qh b/qcsrc/common/gamemodes/gamemode/lms/lms.qh new file mode 100644 index 000000000..288922049 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/lms/lms.qh @@ -0,0 +1,31 @@ +#pragma once + +#ifdef SVQC +#include +#include +.float lms_spectate_warning; +#define autocvar_g_lms_lives_override cvar("g_lms_lives_override") +void lms_Initialize(); + +REGISTER_MUTATOR(lms, false) +{ + MUTATOR_STATIC(); + MUTATOR_ONADD + { + GameRules_limit_score(((!autocvar_g_lms_lives_override) ? -1 : autocvar_g_lms_lives_override)); + GameRules_limit_lead(0); + GameRules_score_enabled(false); + GameRules_scoring(0, 0, 0, { + field(SP_LMS_LIVES, "lives", SFL_SORT_PRIO_SECONDARY); + field(SP_LMS_RANK, "rank", SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY | SFL_ALLOW_HIDE); + }); + + lms_Initialize(); + } + return 0; +} + +// lives related defs +float lms_lowest_lives; +float LMS_NewPlayerLives(); +#endif diff --git a/qcsrc/common/gamemodes/gamemode/race/_mod.inc b/qcsrc/common/gamemodes/gamemode/race/_mod.inc new file mode 100644 index 000000000..73f34a583 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/race/_mod.inc @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/race/_mod.qh b/qcsrc/common/gamemodes/gamemode/race/_mod.qh new file mode 100644 index 000000000..1158df561 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/race/_mod.qh @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/race/race.qc b/qcsrc/common/gamemodes/gamemode/race/race.qc new file mode 100644 index 000000000..c98e1b6a8 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/race/race.qc @@ -0,0 +1,492 @@ +#include "race.qh" + +// TODO: sv_race +#ifdef SVQC +#include + +#define autocvar_g_race_laps_limit cvar("g_race_laps_limit") +float autocvar_g_race_qualifying_timelimit; +float autocvar_g_race_qualifying_timelimit_override; +int autocvar_g_race_teams; + +// legacy bot roles +.float race_checkpoint; +void havocbot_role_race(entity this) +{ + if(IS_DEAD(this)) + return; + + if (navigation_goalrating_timeout(this)) + { + navigation_goalrating_start(this); + + bool raw_touch_check = true; + int cp = this.race_checkpoint; + + LABEL(search_racecheckpoints) + IL_EACH(g_racecheckpoints, true, + { + if(it.cnt == cp || cp == -1) + { + // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint + // e.g. checkpoint in front of Stormkeep's warpzone + // the same workaround is applied in CTS game mode + if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30)) + { + cp = race_NextCheckpoint(cp); + raw_touch_check = false; + goto search_racecheckpoints; + } + navigation_routerating(this, it, 1000000, 5000); + } + }); + + navigation_goalrating_end(this); + + navigation_goalrating_timeout_set(this); + } +} + +void race_ScoreRules() +{ + GameRules_score_enabled(false); + GameRules_scoring(race_teams, 0, 0, { + if (race_teams) { + field_team(ST_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY); + field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY); + field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME); + field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME); + } else if (g_race_qualifying) { + field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME); + } else { + field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY); + field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME); + field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME); + } + }); +} + +void race_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later +{ + if(autocvar_sv_eventlog) + GameLogEcho(strcat(":race:", mode, ":", ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : ""))); +} + +float WinningCondition_Race(float fraglimit) +{ + float wc; + float n, c; + + n = 0; + c = 0; + FOREACH_CLIENT(IS_PLAYER(it), { + ++n; + if(CS(it).race_completed) + ++c; + }); + if(n && (n == c)) + return WINNING_YES; + wc = WinningCondition_Scores(fraglimit, 0); + + // ALWAYS initiate overtime, unless EVERYONE has finished the race! + if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME) + // do NOT support equality when the laps are all raced! + return WINNING_STARTSUDDENDEATHOVERTIME; + else + return WINNING_NEVER; +} + +float WinningCondition_QualifyingThenRace(float limit) +{ + float wc; + wc = WinningCondition_Scores(limit, 0); + + // NEVER initiate overtime + if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME) + { + return WINNING_YES; + } + + return wc; +} + +MUTATOR_HOOKFUNCTION(rc, ClientKill) +{ + if(g_race_qualifying) + M_ARGV(1, float) = 0; // killtime +} + +MUTATOR_HOOKFUNCTION(rc, AbortSpeedrun) +{ + entity player = M_ARGV(0, entity); + + if(autocvar_g_allow_checkpoints) + race_PreparePlayer(player); // nice try +} + +MUTATOR_HOOKFUNCTION(rc, PlayerPhysics) +{ + entity player = M_ARGV(0, entity); + float dt = M_ARGV(1, float); + + player.race_movetime_frac += dt; + float f = floor(player.race_movetime_frac); + player.race_movetime_frac -= f; + player.race_movetime_count += f; + player.race_movetime = player.race_movetime_frac + player.race_movetime_count; + +#ifdef SVQC + if(IS_PLAYER(player)) + { + if (player.race_penalty) + if (time > player.race_penalty) + player.race_penalty = 0; + if(player.race_penalty) + { + player.velocity = '0 0 0'; + set_movetype(player, MOVETYPE_NONE); + player.disableclientprediction = 2; + } + } +#endif + + // force kbd movement for fairness + float wishspeed; + vector wishvel; + + // if record times matter + // ensure nothing EVIL is being done (i.e. div0_evade) + // this hinders joystick users though + // but it still gives SOME analog control + wishvel.x = fabs(CS(player).movement.x); + wishvel.y = fabs(CS(player).movement.y); + if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y) + { + wishvel.z = 0; + wishspeed = vlen(wishvel); + if(wishvel.x >= 2 * wishvel.y) + { + // pure X motion + if(CS(player).movement.x > 0) + CS(player).movement_x = wishspeed; + else + CS(player).movement_x = -wishspeed; + CS(player).movement_y = 0; + } + else if(wishvel.y >= 2 * wishvel.x) + { + // pure Y motion + CS(player).movement_x = 0; + if(CS(player).movement.y > 0) + CS(player).movement_y = wishspeed; + else + CS(player).movement_y = -wishspeed; + } + else + { + // diagonal + if(CS(player).movement.x > 0) + CS(player).movement_x = M_SQRT1_2 * wishspeed; + else + CS(player).movement_x = -M_SQRT1_2 * wishspeed; + if(CS(player).movement.y > 0) + CS(player).movement_y = M_SQRT1_2 * wishspeed; + else + CS(player).movement_y = -M_SQRT1_2 * wishspeed; + } + } +} + +MUTATOR_HOOKFUNCTION(rc, reset_map_global) +{ + float s; + + Score_NicePrint(NULL); + + race_ClearRecords(); + PlayerScore_Sort(race_place, 0, 1, 0); + + FOREACH_CLIENT(true, { + if(it.race_place) + { + s = GameRules_scoring_add(it, RACE_FASTEST, 0); + if(!s) + it.race_place = 0; + } + race_EventLog(ftos(it.race_place), it); + }); + + if(g_race_qualifying == 2) + { + g_race_qualifying = 0; + independent_players = 0; + cvar_set("fraglimit", ftos(race_fraglimit)); + cvar_set("leadlimit", ftos(race_leadlimit)); + cvar_set("timelimit", ftos(race_timelimit)); + race_ScoreRules(); + } +} + +MUTATOR_HOOKFUNCTION(rc, ClientConnect) +{ + entity player = M_ARGV(0, entity); + + race_PreparePlayer(player); + player.race_checkpoint = -1; + + string rr = RACE_RECORD; + + if(IS_REAL_CLIENT(player)) + { + msg_entity = player; + race_send_recordtime(MSG_ONE); + race_send_speedaward(MSG_ONE); + + speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"))); + speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"))); + race_send_speedaward_alltimebest(MSG_ONE); + + float i; + int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt); + race_send_rankings_cnt(MSG_ONE); + for (i = 1; i <= m; ++i) + { + race_SendRankings(i, 0, 0, MSG_ONE); + } + } +} + +MUTATOR_HOOKFUNCTION(rc, MakePlayerObserver) +{ + entity player = M_ARGV(0, entity); + + if(g_race_qualifying) + if(GameRules_scoring_add(player, RACE_FASTEST, 0)) + player.frags = FRAGS_LMS_LOSER; + else + player.frags = FRAGS_SPECTATOR; + + race_PreparePlayer(player); + player.race_checkpoint = -1; +} + +MUTATOR_HOOKFUNCTION(rc, PlayerSpawn) +{ + entity player = M_ARGV(0, entity); + entity spawn_spot = M_ARGV(1, entity); + + if(spawn_spot.target == "") + // Emergency: this wasn't a real spawnpoint. Can this ever happen? + race_PreparePlayer(player); + + // if we need to respawn, do it right + player.race_respawn_checkpoint = player.race_checkpoint; + player.race_respawn_spotref = spawn_spot; + + player.race_place = 0; +} + +MUTATOR_HOOKFUNCTION(rc, PutClientInServer) +{ + entity player = M_ARGV(0, entity); + + if(IS_PLAYER(player)) + if(!game_stopped) + { + if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn + race_PreparePlayer(player); + else // respawn + race_RetractPlayer(player); + + race_AbandonRaceCheck(player); + } +} + +MUTATOR_HOOKFUNCTION(rc, PlayerDies) +{ + entity frag_target = M_ARGV(2, entity); + + frag_target.respawn_flags |= RESPAWN_FORCE; + race_AbandonRaceCheck(frag_target); +} + +MUTATOR_HOOKFUNCTION(rc, HavocBot_ChooseRole) +{ + entity bot = M_ARGV(0, entity); + + bot.havocbot_role = havocbot_role_race; + return true; +} + +MUTATOR_HOOKFUNCTION(rc, GetPressedKeys) +{ + entity player = M_ARGV(0, entity); + + if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1) + { + if (!player.stored_netname) + player.stored_netname = strzone(uid2name(player.crypto_idfp)); + if(player.stored_netname != player.netname) + { + db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname); + strcpy(player.stored_netname, player.netname); + } + } + + if (!IS_OBSERVER(player)) + { + if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed)) + { + speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1'); + speedaward_holder = player.netname; + speedaward_uid = player.crypto_idfp; + speedaward_lastupdate = time; + } + if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1) + { + string rr = RACE_RECORD; + race_send_speedaward(MSG_ALL); + speedaward_lastsent = speedaward_speed; + if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "") + { + speedaward_alltimebest = speedaward_speed; + speedaward_alltimebest_holder = speedaward_holder; + speedaward_alltimebest_uid = speedaward_uid; + db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest)); + db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid); + race_send_speedaward_alltimebest(MSG_ALL); + } + } + } +} + +MUTATOR_HOOKFUNCTION(rc, ForbidPlayerScore_Clear) +{ + if(g_race_qualifying) + return true; // in qualifying, you don't lose score by observing +} + +MUTATOR_HOOKFUNCTION(rc, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE) +{ + M_ARGV(0, float) = race_teams; +} + +MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining) +{ + // announce remaining frags if not in qualifying mode + if(!g_race_qualifying) + return true; +} + +MUTATOR_HOOKFUNCTION(rc, GetRecords) +{ + int record_page = M_ARGV(0, int); + string ret_string = M_ARGV(1, string); + + for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i) + { + if(MapInfo_Get_ByID(i)) + { + float r = race_readTime(MapInfo_Map_bspname, 1); + + if(!r) + continue; + + string h = race_readName(MapInfo_Map_bspname, 1); + ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n"); + } + } + + M_ARGV(1, string) = ret_string; +} + +MUTATOR_HOOKFUNCTION(rc, HideTeamNagger) +{ + return true; // doesn't work so well +} + +MUTATOR_HOOKFUNCTION(rc, FixClientCvars) +{ + entity player = M_ARGV(0, entity); + + stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n"); +} + +MUTATOR_HOOKFUNCTION(rc, CheckRules_World) +{ + float checkrules_timelimit = M_ARGV(1, float); + float checkrules_fraglimit = M_ARGV(2, float); + + if(checkrules_timelimit >= 0) + { + if(!g_race_qualifying) + { + M_ARGV(0, float) = WinningCondition_Race(checkrules_fraglimit); + return true; + } + else if(g_race_qualifying == 2) + { + M_ARGV(0, float) = WinningCondition_QualifyingThenRace(checkrules_fraglimit); + return true; + } + } +} + +MUTATOR_HOOKFUNCTION(rc, ReadLevelCvars) +{ + if(g_race_qualifying == 2) + warmup_stage = 0; +} + +void race_Initialize() +{ + race_ScoreRules(); + if(g_race_qualifying == 2) + warmup_stage = 0; +} + +void rc_SetLimits() +{ + int fraglimit_override, leadlimit_override; + float timelimit_override, qualifying_override; + + if(autocvar_g_race_teams) + { + GameRules_teams(true); + race_teams = BITS(bound(2, autocvar_g_race_teams, 4)); + } + else + race_teams = 0; + + qualifying_override = autocvar_g_race_qualifying_timelimit_override; + fraglimit_override = autocvar_g_race_laps_limit; + leadlimit_override = 0; // currently not supported by race + timelimit_override = autocvar_timelimit_override; + + float want_qualifying = ((qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit) > 0; + + if(autocvar_g_campaign) + { + g_race_qualifying = 1; + independent_players = 1; + } + else if(want_qualifying) + { + g_race_qualifying = 2; + independent_players = 1; + race_fraglimit = (fraglimit_override >= 0) ? fraglimit_override : autocvar_fraglimit; + race_leadlimit = (leadlimit_override >= 0) ? leadlimit_override : autocvar_leadlimit; + race_timelimit = (timelimit_override >= 0) ? timelimit_override : autocvar_timelimit; + qualifying_override = (qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit; + fraglimit_override = 0; + leadlimit_override = 0; + timelimit_override = qualifying_override; + } + else + g_race_qualifying = 0; + GameRules_limit_score(fraglimit_override); + GameRules_limit_lead(leadlimit_override); + GameRules_limit_time(timelimit_override); + GameRules_limit_time_qualifying(qualifying_override); +} +#endif diff --git a/qcsrc/common/gamemodes/gamemode/race/race.qh b/qcsrc/common/gamemodes/gamemode/race/race.qh new file mode 100644 index 000000000..ad966af72 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/race/race.qh @@ -0,0 +1,19 @@ +#pragma once + +#ifdef SVQC +#include +void rc_SetLimits(); +void race_Initialize(); + +REGISTER_MUTATOR(rc, false) +{ + MUTATOR_STATIC(); + MUTATOR_ONADD + { + rc_SetLimits(); + + race_Initialize(); + } + return 0; +} +#endif diff --git a/qcsrc/common/gamemodes/gamemode/tdm/_mod.inc b/qcsrc/common/gamemodes/gamemode/tdm/_mod.inc new file mode 100644 index 000000000..ef7137a00 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/tdm/_mod.inc @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/tdm/_mod.qh b/qcsrc/common/gamemodes/gamemode/tdm/_mod.qh new file mode 100644 index 000000000..f1965c109 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/tdm/_mod.qh @@ -0,0 +1,2 @@ +// generated file; do not modify +#include diff --git a/qcsrc/common/gamemodes/gamemode/tdm/tdm.qc b/qcsrc/common/gamemodes/gamemode/tdm/tdm.qc new file mode 100644 index 000000000..39e5fec1e --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/tdm/tdm.qc @@ -0,0 +1,67 @@ +#include "tdm.qh" + +// TODO: sv_tdm +// TODO? rename to teamdeathmatch +#ifdef SVQC +int autocvar_g_tdm_teams; +int autocvar_g_tdm_teams_override; + +/*QUAKED spawnfunc_tdm_team (0 .5 .8) (-16 -16 -24) (16 16 32) +Team declaration for TDM gameplay, this allows you to decide what team names and control point models are used in your map. +Note: If you use spawnfunc_tdm_team entities you must define at least 2! However, unlike domination, you don't need to make a blank one too. +Keys: +"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)... +"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */ +spawnfunc(tdm_team) +{ + if(!g_tdm || !this.cnt) { delete(this); return; } + + this.classname = "tdm_team"; + this.team = this.cnt + 1; +} + +// code from here on is just to support maps that don't have team entities +void tdm_SpawnTeam (string teamname, int teamcolor) +{ + entity this = new_pure(tdm_team); + this.netname = teamname; + this.cnt = teamcolor - 1; + this.team = teamcolor; + this.spawnfunc_checked = true; + //spawnfunc_tdm_team(this); +} + +void tdm_DelayedInit(entity this) +{ + // if no teams are found, spawn defaults + if(find(NULL, classname, "tdm_team") == NULL) + { + LOG_TRACE("No \"tdm_team\" entities found on this map, creating them anyway."); + + int numteams = autocvar_g_tdm_teams_override; + if(numteams < 2) { numteams = autocvar_g_tdm_teams; } + + int teams = BITS(bound(2, numteams, 4)); + if(teams & BIT(0)) + tdm_SpawnTeam("Red", NUM_TEAM_1); + if(teams & BIT(1)) + tdm_SpawnTeam("Blue", NUM_TEAM_2); + if(teams & BIT(2)) + tdm_SpawnTeam("Yellow", NUM_TEAM_3); + if(teams & BIT(3)) + tdm_SpawnTeam("Pink", NUM_TEAM_4); + } +} + +MUTATOR_HOOKFUNCTION(tdm, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE) +{ + M_ARGV(1, string) = "tdm_team"; + return true; +} + +MUTATOR_HOOKFUNCTION(tdm, Scores_CountFragsRemaining) +{ + // announce remaining frags + return true; +} +#endif diff --git a/qcsrc/common/gamemodes/gamemode/tdm/tdm.qh b/qcsrc/common/gamemodes/gamemode/tdm/tdm.qh new file mode 100644 index 000000000..1c8674a01 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/tdm/tdm.qh @@ -0,0 +1,24 @@ +#pragma once + +#ifdef SVQC +#include +int autocvar_g_tdm_point_limit; +int autocvar_g_tdm_point_leadlimit; +bool autocvar_g_tdm_team_spawns; +void tdm_DelayedInit(entity this); + +REGISTER_MUTATOR(tdm, false) +{ + MUTATOR_STATIC(); + MUTATOR_ONADD + { + GameRules_teams(true); + GameRules_spawning_teams(autocvar_g_tdm_team_spawns); + GameRules_limit_score(autocvar_g_tdm_point_limit); + GameRules_limit_lead(autocvar_g_tdm_point_leadlimit); + + InitializeEntity(NULL, tdm_DelayedInit, INITPRIO_GAMETYPE); + } + return 0; +} +#endif diff --git a/qcsrc/common/gamemodes/sv_rules.qh b/qcsrc/common/gamemodes/sv_rules.qh index 35a643d53..979477cba 100644 --- a/qcsrc/common/gamemodes/sv_rules.qh +++ b/qcsrc/common/gamemodes/sv_rules.qh @@ -1,5 +1,9 @@ #pragma once +// TODO: find a better location for these? +float total_players; +float redalive, bluealive, yellowalive, pinkalive; + // todo: accept the number of teams as a parameter void GameRules_teams(bool value); diff --git a/qcsrc/common/items/inventory.qh b/qcsrc/common/items/inventory.qh index 852007501..9075c0912 100644 --- a/qcsrc/common/items/inventory.qh +++ b/qcsrc/common/items/inventory.qh @@ -21,9 +21,11 @@ const int Inventory_groups_minor = 8; // ceil(Items_MAX / Inventory_groups_major #define G_MINOR(id) ((id) % Inventory_groups_minor) #ifdef CSQC +Inventory g_inventory; NET_HANDLE(ENT_CLIENT_INVENTORY, bool isnew) { make_pure(this); + g_inventory = this; const int majorBits = ReadShort(); for (int i = 0; i < Inventory_groups_major; ++i) { if (!(majorBits & BIT(i))) { diff --git a/qcsrc/common/items/item.qh b/qcsrc/common/items/item.qh index e59152076..31b8f43cb 100644 --- a/qcsrc/common/items/item.qh +++ b/qcsrc/common/items/item.qh @@ -72,9 +72,7 @@ const int IT_PICKUPMASK = IT_UNLIMITED_AMMO | IT_JETPACK | IT_FU enum { ITEM_FLAG_NORMAL = BIT(0), ///< Item is usable during normal gameplay. - ITEM_FLAG_INSTAGIB = BIT(1), ///< Item is usable in instagib. - ITEM_FLAG_OVERKILL = BIT(2), ///< Item is usable in overkill. - ITEM_FLAG_MUTATORBLOCKED = BIT(3) + ITEM_FLAG_MUTATORBLOCKED = BIT(1) }; #define ITEM_HANDLE(signal, ...) __Item_Send_##signal(__VA_ARGS__) diff --git a/qcsrc/common/items/item/ammo.qh b/qcsrc/common/items/item/ammo.qh index 1d5bd87ba..7c5c12af7 100644 --- a/qcsrc/common/items/item/ammo.qh +++ b/qcsrc/common/items/item/ammo.qh @@ -38,7 +38,7 @@ MODEL(Bullets_ITEM, Item_Model("a_bullets.mdl")); #ifdef SVQC PROPERTY(int, g_pickup_nails); -void ammo_bullets_init(entity item) +void ammo_bullets_init(Pickup this, entity item) { if(!item.ammo_nails) item.ammo_nails = g_pickup_nails; @@ -72,7 +72,7 @@ MODEL(Cells_ITEM, Item_Model("a_cells.md3")); #ifdef SVQC PROPERTY(int, g_pickup_cells); -void ammo_cells_init(entity item) +void ammo_cells_init(Pickup this, entity item) { if(!item.ammo_cells) item.ammo_cells = g_pickup_cells; @@ -102,7 +102,7 @@ MODEL(Plasma_ITEM, Item_Model("a_cells.md3")); #ifdef SVQC PROPERTY(int, g_pickup_plasma); -void ammo_plasma_init(entity item) +void ammo_plasma_init(Pickup this, entity item) { if(!item.ammo_plasma) item.ammo_plasma = g_pickup_plasma; @@ -132,7 +132,7 @@ MODEL(Rockets_ITEM, Item_Model("a_rockets.md3")); #ifdef SVQC PROPERTY(int, g_pickup_rockets); -void ammo_rockets_init(entity item) +void ammo_rockets_init(Pickup this, entity item) { if(!item.ammo_rockets) item.ammo_rockets = g_pickup_rockets; @@ -162,7 +162,7 @@ MODEL(Shells_ITEM, Item_Model("a_shells.md3")); #ifdef SVQC PROPERTY(int, g_pickup_shells); -void ammo_shells_init(entity item) +void ammo_shells_init(Pickup this, entity item) { if(!item.ammo_shells) item.ammo_shells = g_pickup_shells; diff --git a/qcsrc/common/items/item/armor.qh b/qcsrc/common/items/item/armor.qh index 7f37c75ae..880a932d7 100644 --- a/qcsrc/common/items/item/armor.qh +++ b/qcsrc/common/items/item/armor.qh @@ -22,7 +22,7 @@ SOUND(ArmorSmall, Item_Sound("armor1")); PROPERTY(float, g_pickup_armorsmall_anyway); PROPERTY(int, g_pickup_armorsmall); PROPERTY(int, g_pickup_armorsmall_max); -void item_armorsmall_init(entity item) +void item_armorsmall_init(Pickup this, entity item) { if(!item.max_armorvalue) item.max_armorvalue = g_pickup_armorsmall_max; @@ -34,7 +34,7 @@ void item_armorsmall_init(entity item) REGISTER_ITEM(ArmorSmall, Armor) { this.m_canonical_spawnfunc = "item_armor_small"; #ifdef GAMEQC - this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL; + this.spawnflags = ITEM_FLAG_NORMAL; this.m_model = MDL_ArmorSmall_ITEM; this.m_sound = SND_ArmorSmall; #endif @@ -60,7 +60,7 @@ SOUND(ArmorMedium, Item_Sound("armor10")); PROPERTY(float, g_pickup_armormedium_anyway); PROPERTY(int, g_pickup_armormedium); PROPERTY(int, g_pickup_armormedium_max); -void item_armormedium_init(entity item) +void item_armormedium_init(Pickup this, entity item) { if(!item.max_armorvalue) item.max_armorvalue = g_pickup_armormedium_max; @@ -72,7 +72,7 @@ void item_armormedium_init(entity item) REGISTER_ITEM(ArmorMedium, Armor) { this.m_canonical_spawnfunc = "item_armor_medium"; #ifdef GAMEQC - this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL; + this.spawnflags = ITEM_FLAG_NORMAL; this.m_model = MDL_ArmorMedium_ITEM; this.m_sound = SND_ArmorMedium; #endif @@ -98,7 +98,7 @@ SOUND(ArmorBig, Item_Sound("armor17_5")); PROPERTY(float, g_pickup_armorbig_anyway); PROPERTY(int, g_pickup_armorbig); PROPERTY(int, g_pickup_armorbig_max); -void item_armorbig_init(entity item) +void item_armorbig_init(Pickup this, entity item) { if(!item.max_armorvalue) item.max_armorvalue = g_pickup_armorbig_max; @@ -110,7 +110,7 @@ void item_armorbig_init(entity item) REGISTER_ITEM(ArmorBig, Armor) { this.m_canonical_spawnfunc = "item_armor_big"; #ifdef GAMEQC - this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL; + this.spawnflags = ITEM_FLAG_NORMAL; this.m_model = MDL_ArmorBig_ITEM; this.m_sound = SND_ArmorBig; #endif @@ -138,7 +138,7 @@ SOUND(ArmorMega, Item_Sound("armor25")); PROPERTY(float, g_pickup_armormega_anyway); PROPERTY(int, g_pickup_armormega); PROPERTY(int, g_pickup_armormega_max); -void item_armormega_init(entity item) +void item_armormega_init(Pickup this, entity item) { if(!item.max_armorvalue) item.max_armorvalue = g_pickup_armormega_max; @@ -150,7 +150,7 @@ void item_armormega_init(entity item) REGISTER_ITEM(ArmorMega, Armor) { this.m_canonical_spawnfunc = "item_armor_mega"; #ifdef GAMEQC - this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL; + this.spawnflags = ITEM_FLAG_NORMAL; this.m_model = MDL_ArmorMega_ITEM; this.m_sound = SND_ArmorMega; #endif diff --git a/qcsrc/common/items/item/health.qh b/qcsrc/common/items/item/health.qh index da431086e..6a5ffc5ca 100644 --- a/qcsrc/common/items/item/health.qh +++ b/qcsrc/common/items/item/health.qh @@ -22,7 +22,7 @@ SOUND(HealthSmall, Item_Sound("minihealth")); PROPERTY(float, g_pickup_healthsmall_anyway); PROPERTY(int, g_pickup_healthsmall); PROPERTY(int, g_pickup_healthsmall_max); -void item_healthsmall_init(entity item) +void item_healthsmall_init(Pickup this, entity item) { if(!item.max_health) item.max_health = g_pickup_healthsmall_max; @@ -60,7 +60,7 @@ SOUND(HealthMedium, Item_Sound("mediumhealth")); PROPERTY(float, g_pickup_healthmedium_anyway); PROPERTY(int, g_pickup_healthmedium); PROPERTY(int, g_pickup_healthmedium_max); -void item_healthmedium_init(entity item) +void item_healthmedium_init(Pickup this, entity item) { if(!item.max_health) item.max_health = g_pickup_healthmedium_max; @@ -98,7 +98,7 @@ SOUND(HealthBig, Item_Sound("mediumhealth")); PROPERTY(float, g_pickup_healthbig_anyway); PROPERTY(int, g_pickup_healthbig); PROPERTY(int, g_pickup_healthbig_max); -void item_healthbig_init(entity item) +void item_healthbig_init(Pickup this, entity item) { if(!item.max_health) item.max_health = g_pickup_healthbig_max; @@ -138,7 +138,7 @@ SOUND(HealthMega, Item_Sound("megahealth")); PROPERTY(float, g_pickup_healthmega_anyway); PROPERTY(int, g_pickup_healthmega); PROPERTY(int, g_pickup_healthmega_max); -void item_healthmega_init(entity item) +void item_healthmega_init(Pickup this, entity item) { if(!item.max_health) item.max_health = g_pickup_healthmega_max; @@ -150,7 +150,7 @@ void item_healthmega_init(entity item) REGISTER_ITEM(HealthMega, Health) { this.m_canonical_spawnfunc = "item_health_mega"; #ifdef GAMEQC - this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL; + this.spawnflags = ITEM_FLAG_NORMAL; this.m_model = MDL_HealthMega_ITEM; this.m_sound = SND_HealthMega; #endif diff --git a/qcsrc/common/items/item/jetpack.qh b/qcsrc/common/items/item/jetpack.qh index 284bf3d39..73f55e83f 100644 --- a/qcsrc/common/items/item/jetpack.qh +++ b/qcsrc/common/items/item/jetpack.qh @@ -17,7 +17,7 @@ MODEL(Jetpack_ITEM, Item_Model("g_jetpack.md3")); #ifdef SVQC PROPERTY(int, g_pickup_fuel_jetpack); -void powerup_jetpack_init(entity item) +void powerup_jetpack_init(Pickup this, entity item) { if(!item.ammo_fuel) item.ammo_fuel = g_pickup_fuel_jetpack; @@ -55,7 +55,7 @@ MODEL(JetpackFuel_ITEM, Item_Model("g_fuel.md3")); #ifdef SVQC PROPERTY(int, g_pickup_fuel); -void ammo_fuel_init(entity item) +void ammo_fuel_init(Pickup this, entity item) { if(!item.ammo_fuel) item.ammo_fuel = g_pickup_fuel; diff --git a/qcsrc/common/items/item/pickup.qh b/qcsrc/common/items/item/pickup.qh index fb4bc28cd..0f09901af 100644 --- a/qcsrc/common/items/item/pickup.qh +++ b/qcsrc/common/items/item/pickup.qh @@ -42,7 +42,7 @@ CLASS(Pickup, GameItem) ATTRIB(Pickup, m_respawntime, float()); ATTRIB(Pickup, m_respawntimejitter, float()); ATTRIB(Pickup, m_pickupanyway, float()); - ATTRIB(Pickup, m_iteminit, void(entity item)); + ATTRIB(Pickup, m_iteminit, void(Pickup this, entity item)); float Item_GiveTo(entity item, entity player); METHOD(Pickup, giveTo, bool(Pickup this, entity item, entity player)); bool ITEM_HANDLE(Pickup, Pickup this, entity item, entity player); diff --git a/qcsrc/common/items/item/powerup.qh b/qcsrc/common/items/item/powerup.qh index fe47b6343..1c10afa48 100644 --- a/qcsrc/common/items/item/powerup.qh +++ b/qcsrc/common/items/item/powerup.qh @@ -24,7 +24,7 @@ SOUND(Strength, Item_Sound("powerup")); #ifdef SVQC float autocvar_g_balance_powerup_strength_time; -void powerup_strength_init(entity item) +void powerup_strength_init(Pickup this, entity item) { if(!item.strength_finished) item.strength_finished = autocvar_g_balance_powerup_strength_time; @@ -60,7 +60,7 @@ SOUND(Shield, Item_Sound("powerup_shield")); #ifdef SVQC float autocvar_g_balance_powerup_invincible_time; -void powerup_shield_init(entity item) +void powerup_shield_init(Pickup this, entity item) { if(!item.invincible_finished) item.invincible_finished = autocvar_g_balance_powerup_invincible_time; diff --git a/qcsrc/common/mapobjects/_mod.inc b/qcsrc/common/mapobjects/_mod.inc new file mode 100644 index 000000000..1aab2b927 --- /dev/null +++ b/qcsrc/common/mapobjects/_mod.inc @@ -0,0 +1,11 @@ +// generated file; do not modify +#include +#include +#include +#include +#include + +#include +#include +#include +#include diff --git a/qcsrc/common/mapobjects/_mod.qh b/qcsrc/common/mapobjects/_mod.qh new file mode 100644 index 000000000..ebb7a4325 --- /dev/null +++ b/qcsrc/common/mapobjects/_mod.qh @@ -0,0 +1,11 @@ +// generated file; do not modify +#include +#include +#include +#include +#include + +#include +#include +#include +#include diff --git a/qcsrc/common/mapobjects/defs.qh b/qcsrc/common/mapobjects/defs.qh new file mode 100644 index 000000000..45afb51f9 --- /dev/null +++ b/qcsrc/common/mapobjects/defs.qh @@ -0,0 +1,41 @@ +#pragma once + +//----------- +// SPAWNFLAGS +//----------- +const int START_ENABLED = BIT(0); +const int START_DISABLED = BIT(0); +const int ALL_ENTITIES = BIT(1); +const int ON_MAPLOAD = BIT(1); +const int INVERT_TEAMS = BIT(2); +const int CRUSH = BIT(2); +const int NOSPLASH = BIT(8); // generic anti-splashdamage spawnflag +const int ONLY_PLAYERS = BIT(14); + +// triggers +const int SPAWNFLAG_NOMESSAGE = BIT(0); +const int SPAWNFLAG_NOTOUCH = BIT(0); + +//---------- +// SENDFLAGS +//---------- +const int SF_TRIGGER_INIT = BIT(0); +const int SF_TRIGGER_UPDATE = BIT(1); +const int SF_TRIGGER_RESET = BIT(2); + +//---------------- +// STATES & ACTIVE +//---------------- +#ifdef CSQC +// this stuff is defined in the server side engine VM, so we must define it separately here +const int STATE_TOP = 0; +const int STATE_BOTTOM = 1; +const int STATE_UP = 2; +const int STATE_DOWN = 3; + +const int ACTIVE_NOT = 0; +const int ACTIVE_ACTIVE = 1; +const int ACTIVE_IDLE = 2; +const int ACTIVE_BUSY = 2; +const int ACTIVE_TOGGLE = 3; +#endif diff --git a/qcsrc/common/mapobjects/func/_mod.inc b/qcsrc/common/mapobjects/func/_mod.inc new file mode 100644 index 000000000..0c82e979a --- /dev/null +++ b/qcsrc/common/mapobjects/func/_mod.inc @@ -0,0 +1,18 @@ +// generated file; do not modify +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/qcsrc/common/mapobjects/func/_mod.qh b/qcsrc/common/mapobjects/func/_mod.qh new file mode 100644 index 000000000..052edb954 --- /dev/null +++ b/qcsrc/common/mapobjects/func/_mod.qh @@ -0,0 +1,18 @@ +// generated file; do not modify +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/qcsrc/common/mapobjects/func/bobbing.qc b/qcsrc/common/mapobjects/func/bobbing.qc new file mode 100644 index 000000000..b647e15a8 --- /dev/null +++ b/qcsrc/common/mapobjects/func/bobbing.qc @@ -0,0 +1,85 @@ +#include "bobbing.qh" +#ifdef SVQC +.float height; +void func_bobbing_controller_think(entity this) +{ + vector v; + this.nextthink = time + 0.1; + + if(this.owner.active != ACTIVE_ACTIVE) + { + this.owner.velocity = '0 0 0'; + return; + } + + // calculate sinewave using makevectors + makevectors((this.nextthink * this.owner.cnt + this.owner.phase * 360) * '0 1 0'); + v = this.owner.destvec + this.owner.movedir * v_forward_y; + if(this.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed + // * 10 so it will arrive in 0.1 sec + this.owner.velocity = (v - this.owner.origin) * 10; +} + +/*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS +Brush model that moves back and forth on one axis (default Z). +speed : how long one cycle takes in seconds (default 4) +height : how far the cycle moves (default 32) +phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0) +noise : path/name of looping .wav file to play. +dmg : Do this mutch dmg every .dmgtime intervall when blocked +dmgtime : See above. +*/ +spawnfunc(func_bobbing) +{ + entity controller; + if (this.noise != "") + { + precache_sound(this.noise); + soundto(MSG_INIT, this, CH_TRIGGER_SINGLE, this.noise, VOL_BASE, ATTEN_IDLE); + } + if (!this.speed) + this.speed = 4; + if (!this.height) + this.height = 32; + // center of bobbing motion + this.destvec = this.origin; + // time scale to get degrees + this.cnt = 360 / this.speed; + + this.active = ACTIVE_ACTIVE; + + // damage when blocked + setblocked(this, generic_plat_blocked); + if(this.dmg && (this.message == "")) + this.message = " was squished"; + if(this.dmg && (this.message2 == "")) + this.message2 = "was squished by"; + if(this.dmg && (!this.dmgtime)) + this.dmgtime = 0.25; + this.dmgtime2 = time; + + // how far to bob + if (this.spawnflags & BOBBING_XAXIS) + this.movedir = '1 0 0' * this.height; + else if (this.spawnflags & BOBBING_YAXIS) + this.movedir = '0 1 0' * this.height; + else // Z + this.movedir = '0 0 1' * this.height; + + if (!InitMovingBrushTrigger(this)) + return; + + // wait for targets to spawn + controller = new(func_bobbing_controller); + controller.owner = this; + controller.nextthink = time + 1; + setthink(controller, func_bobbing_controller_think); + this.nextthink = this.ltime + 999999999; + setthink(this, SUB_NullThink); + + // Savage: Reduce bandwith, critical on e.g. nexdm02 + this.effects |= EF_LOWPRECISION; + + // TODO make a reset function for this one +} +#endif diff --git a/qcsrc/common/mapobjects/func/bobbing.qh b/qcsrc/common/mapobjects/func/bobbing.qh new file mode 100644 index 000000000..58f7bb714 --- /dev/null +++ b/qcsrc/common/mapobjects/func/bobbing.qh @@ -0,0 +1,5 @@ +#pragma once + + +const int BOBBING_XAXIS = BIT(0); +const int BOBBING_YAXIS = BIT(1); diff --git a/qcsrc/common/mapobjects/func/breakable.qc b/qcsrc/common/mapobjects/func/breakable.qc new file mode 100644 index 000000000..d8f6cb138 --- /dev/null +++ b/qcsrc/common/mapobjects/func/breakable.qc @@ -0,0 +1,373 @@ +#include "breakable.qh" +#ifdef SVQC + +#include +#include +#include +#include +#include + +.entity sprite; + +.float dmg; +.float dmg_edge; +.float dmg_radius; +.float dmg_force; +.float debrismovetype; +.float debrissolid; +.vector debrisvelocity; +.vector debrisvelocityjitter; +.vector debrisavelocityjitter; +.float debristime; +.float debristimejitter; +.float debrisfadetime; +.float debrisdamageforcescale; +.float debrisskin; + +.string mdl_dead; // or "" to hide when broken +.string debris; // space separated list of debris models +// other fields: +// mdl = particle effect name +// count = particle effect multiplier +// targetname = target to trigger to unbreak the model +// target = targets to trigger when broken +// health = amount of damage it can take +// spawnflags: +// START_DISABLED: needs to be triggered to activate +// BREAKABLE_INDICATE_DAMAGE: indicate damage +// BREAKABLE_NODAMAGE: don't take direct damage (needs to be triggered to 'explode', then triggered again to restore) +// NOSPLASH: don't take splash damage +// notes: +// for mdl_dead to work, origin must be set (using a common/origin brush). +// Otherwise mdl_dead will be displayed at the map origin, and nobody would +// want that! + +void func_breakable_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force); + +// +// func_breakable +// - basically func_assault_destructible for general gameplay use +// +void LaunchDebris (entity this, string debrisname, vector force) +{ + entity dbr = spawn(); + vector org = this.absmin + + '1 0 0' * random() * (this.absmax.x - this.absmin.x) + + '0 1 0' * random() * (this.absmax.y - this.absmin.y) + + '0 0 1' * random() * (this.absmax.z - this.absmin.z); + setorigin(dbr, org); + _setmodel (dbr, debrisname ); + dbr.skin = this.debrisskin; + dbr.colormap = this.colormap; // inherit team colors + dbr.owner = this; // do not be affected by our own explosion + set_movetype(dbr, this.debrismovetype); + dbr.solid = this.debrissolid; + if(dbr.solid != SOLID_BSP) // SOLID_BSP has exact collision, MAYBE this works? TODO check this out + setsize(dbr, '0 0 0', '0 0 0'); // needed for performance, until engine can deal better with it + dbr.velocity_x = this.debrisvelocity.x + this.debrisvelocityjitter.x * crandom(); + dbr.velocity_y = this.debrisvelocity.y + this.debrisvelocityjitter.y * crandom(); + dbr.velocity_z = this.debrisvelocity.z + this.debrisvelocityjitter.z * crandom(); + dbr.velocity = dbr.velocity + force * this.debrisdamageforcescale; + dbr.angles = this.angles; + dbr.avelocity_x = random()*this.debrisavelocityjitter.x; + dbr.avelocity_y = random()*this.debrisavelocityjitter.y; + dbr.avelocity_z = random()*this.debrisavelocityjitter.z; + dbr.damageforcescale = this.debrisdamageforcescale; + if(dbr.damageforcescale) + dbr.takedamage = DAMAGE_YES; + SUB_SetFade(dbr, time + this.debristime + crandom() * this.debristimejitter, this.debrisfadetime); +} + +void func_breakable_colormod(entity this) +{ + float h; + if (!(this.spawnflags & BREAKABLE_INDICATE_DAMAGE)) + return; + h = this.health / this.max_health; + if(h < 0.25) + this.colormod = '1 0 0'; + else if(h <= 0.75) + this.colormod = '1 0 0' + '0 1 0' * (2 * h - 0.5); + else + this.colormod = '1 1 1'; +} + +void func_breakable_look_destroyed(entity this) +{ + float floorZ; + + if(this.solid == SOLID_BSP) // in case a misc_follow moved me, save the current origin first + this.dropped_origin = this.origin; + + if(this.mdl_dead == "") + this.effects |= EF_NODRAW; + else { + if (this.origin == '0 0 0') { // probably no origin brush, so don't spawn in the middle of the map.. + floorZ = this.absmin.z; + setorigin(this, ((this.absmax + this.absmin) * 0.5)); + this.origin_z = floorZ; + } + _setmodel(this, this.mdl_dead); + ApplyMinMaxScaleAngles(this); + this.effects &= ~EF_NODRAW; + } + + this.solid = SOLID_NOT; +} + +void func_breakable_look_restore(entity this) +{ + _setmodel(this, this.mdl); + ApplyMinMaxScaleAngles(this); + this.effects &= ~EF_NODRAW; + + if(this.mdl_dead != "") // only do this if we use mdl_dead, to behave better with misc_follow + setorigin(this, this.dropped_origin); + + this.solid = SOLID_BSP; +} + +void func_breakable_behave_destroyed(entity this) +{ + this.health = this.max_health; + this.takedamage = DAMAGE_NO; + if(this.bot_attack) + IL_REMOVE(g_bot_targets, this); + this.bot_attack = false; + this.event_damage = func_null; + this.state = STATE_BROKEN; + if(this.spawnflags & BREAKABLE_NODAMAGE) + this.use = func_null; + func_breakable_colormod(this); + if (this.noise1) + stopsound (this, CH_TRIGGER_SINGLE); +} + +void func_breakable_think(entity this) +{ + this.nextthink = time; + CSQCMODEL_AUTOUPDATE(this); +} + +void func_breakable_destroy(entity this, entity actor, entity trigger); +void func_breakable_behave_restore(entity this) +{ + this.health = this.max_health; + if(this.sprite) + { + WaypointSprite_UpdateMaxHealth(this.sprite, this.max_health); + WaypointSprite_UpdateHealth(this.sprite, this.health); + } + if(!(this.spawnflags & BREAKABLE_NODAMAGE)) + { + this.takedamage = DAMAGE_AIM; + if(!this.bot_attack) + IL_PUSH(g_bot_targets, this); + this.bot_attack = true; + this.event_damage = func_breakable_damage; + } + if(this.spawnflags & BREAKABLE_NODAMAGE) + this.use = func_breakable_destroy; // don't need to set it usually, as .use isn't reset + this.state = STATE_ALIVE; + //this.nextthink = 0; // cancel auto respawn + setthink(this, func_breakable_think); + this.nextthink = time + 0.1; + func_breakable_colormod(this); + if (this.noise1) + _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM); +} + +void func_breakable_init_for_player(entity this, entity player) +{ + if (this.noise1 && this.state == STATE_ALIVE && IS_REAL_CLIENT(player)) + { + msg_entity = player; + soundto (MSG_ONE, this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM); + } +} + +void func_breakable_destroyed(entity this) +{ + func_breakable_look_destroyed(this); + func_breakable_behave_destroyed(this); +} + +void func_breakable_restore(entity this, entity actor, entity trigger) +{ + func_breakable_look_restore(this); + func_breakable_behave_restore(this); +} + +void func_breakable_restore_self(entity this) +{ + func_breakable_restore(this, NULL, NULL); +} + +vector debrisforce; // global, set before calling this +void func_breakable_destroy(entity this, entity actor, entity trigger) +{ + float n, i; + string oldmsg; + + entity act = this.owner; + this.owner = NULL; // set by W_PrepareExplosionByDamage + + // now throw around the debris + n = tokenize_console(this.debris); + for(i = 0; i < n; ++i) + LaunchDebris(this, argv(i), debrisforce); + + func_breakable_destroyed(this); + + if(this.noise) + _sound (this, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM); + + if(this.dmg) + RadiusDamage(this, act, this.dmg, this.dmg_edge, this.dmg_radius, this, NULL, this.dmg_force, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, NULL); + + if(this.cnt) // TODO + __pointparticles(this.cnt, this.absmin * 0.5 + this.absmax * 0.5, '0 0 0', this.count); + + if(this.respawntime) + { + CSQCMODEL_AUTOUPDATE(this); + setthink(this, func_breakable_restore_self); + this.nextthink = time + this.respawntime + crandom() * this.respawntimejitter; + } + + oldmsg = this.message; + this.message = ""; + SUB_UseTargets(this, act, trigger); + this.message = oldmsg; +} + +void func_breakable_destroy_self(entity this) +{ + func_breakable_destroy(this, NULL, NULL); +} + +void func_breakable_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) +{ + if(this.state == STATE_BROKEN) + return; + if(this.spawnflags & NOSPLASH) + if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH)) + return; + if(this.team) + if(attacker.team == this.team) + return; + this.pain_finished = time; + this.health = this.health - damage; + if(this.sprite) + { + WaypointSprite_Ping(this.sprite); + WaypointSprite_UpdateHealth(this.sprite, this.health); + } + func_breakable_colormod(this); + + if(this.health <= 0) + { + debrisforce = force; + + this.takedamage = DAMAGE_NO; + this.event_damage = func_null; + + if(IS_CLIENT(attacker)) //&& this.classname == "func_assault_destructible") + { + this.owner = attacker; + this.realowner = attacker; + } + + // do not explode NOW but in the NEXT FRAME! + // because recursive calls to RadiusDamage are not allowed + this.nextthink = time; + CSQCMODEL_AUTOUPDATE(this); + setthink(this, func_breakable_destroy_self); + } +} + +void func_breakable_reset(entity this) +{ + this.team = this.team_saved; + func_breakable_look_restore(this); + if(this.spawnflags & START_DISABLED) + func_breakable_behave_destroyed(this); + else + func_breakable_behave_restore(this); +} + +// destructible walls that can be used to trigger target_objective_decrease +spawnfunc(func_breakable) +{ + float n, i; + if(!this.health) + this.health = 100; + this.max_health = this.health; + + // yes, I know, MOVETYPE_NONE is not available here, not that one would want it here anyway + if(!this.debrismovetype) this.debrismovetype = MOVETYPE_BOUNCE; + if(!this.debrissolid) this.debrissolid = SOLID_NOT; + if(this.debrisvelocity == '0 0 0') this.debrisvelocity = '0 0 140'; + if(this.debrisvelocityjitter == '0 0 0') this.debrisvelocityjitter = '70 70 70'; + if(this.debrisavelocityjitter == '0 0 0') this.debrisavelocityjitter = '600 600 600'; + if(!this.debristime) this.debristime = 3.5; + if(!this.debristimejitter) this.debristime = 2.5; + + if(this.mdl != "") + this.cnt = _particleeffectnum(this.mdl); + if(this.count == 0) + this.count = 1; + + if(this.message == "") + this.message = "got too close to an explosion"; + if(this.message2 == "") + this.message2 = "was pushed into an explosion by"; + if(!this.dmg_radius) + this.dmg_radius = 150; + if(!this.dmg_force) + this.dmg_force = 200; + + this.mdl = this.model; + SetBrushEntityModel(this); + + if(this.spawnflags & BREAKABLE_NODAMAGE) + this.use = func_breakable_destroy; + else + this.use = func_breakable_restore; + + if(this.spawnflags & BREAKABLE_NODAMAGE) + { + this.takedamage = DAMAGE_NO; + this.event_damage = func_null; + this.bot_attack = false; + } + + // precache all the models + if (this.mdl_dead) + precache_model(this.mdl_dead); + n = tokenize_console(this.debris); + for(i = 0; i < n; ++i) + precache_model(argv(i)); + if(this.noise) + precache_sound(this.noise); + if(this.noise1) + precache_sound(this.noise1); + + this.team_saved = this.team; + IL_PUSH(g_saved_team, this); + this.dropped_origin = this.origin; + + this.reset = func_breakable_reset; + this.reset(this); + + IL_PUSH(g_initforplayer, this); + this.init_for_player = func_breakable_init_for_player; + + CSQCMODEL_AUTOINIT(this); +} + +// for use in maps with a "model" key set +spawnfunc(misc_breakablemodel) { + spawnfunc_func_breakable(this); +} +#endif diff --git a/qcsrc/common/mapobjects/func/breakable.qh b/qcsrc/common/mapobjects/func/breakable.qh new file mode 100644 index 000000000..0efbcfaed --- /dev/null +++ b/qcsrc/common/mapobjects/func/breakable.qh @@ -0,0 +1,12 @@ +#pragma once + + +const int BREAKABLE_INDICATE_DAMAGE = BIT(1); +const int BREAKABLE_NODAMAGE = BIT(2); + +const int STATE_ALIVE = 0; +const int STATE_BROKEN = 1; + +#ifdef SVQC +spawnfunc(func_breakable); +#endif diff --git a/qcsrc/common/mapobjects/func/button.qc b/qcsrc/common/mapobjects/func/button.qc new file mode 100644 index 000000000..28e6481c8 --- /dev/null +++ b/qcsrc/common/mapobjects/func/button.qc @@ -0,0 +1,170 @@ +#include "button.qh" +#ifdef SVQC +// button and multiple button + +void button_wait(entity this); +void button_return(entity this); + +void button_wait(entity this) +{ + this.state = STATE_TOP; + if(this.wait >= 0) + { + this.nextthink = this.ltime + this.wait; + setthink(this, button_return); + } + SUB_UseTargets(this, this.enemy, NULL); + this.frame = 1; // use alternate textures +} + +void button_done(entity this) +{ + this.state = STATE_BOTTOM; +} + +void button_return(entity this) +{ + this.state = STATE_DOWN; + SUB_CalcMove (this, this.pos1, TSPEED_LINEAR, this.speed, button_done); + this.frame = 0; // use normal textures + if (this.health) + this.takedamage = DAMAGE_YES; // can be shot again +} + + +void button_blocked(entity this, entity blocker) +{ + // do nothing, just don't come all the way back out +} + + +void button_fire(entity this) +{ + this.health = this.max_health; + this.takedamage = DAMAGE_NO; // will be reset upon return + + if (this.state == STATE_UP || this.state == STATE_TOP) + return; + + if (this.noise != "") + _sound (this, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM); + + this.state = STATE_UP; + SUB_CalcMove (this, this.pos2, TSPEED_LINEAR, this.speed, button_wait); +} + +void button_reset(entity this) +{ + this.health = this.max_health; + setorigin(this, this.pos1); + this.frame = 0; // use normal textures + this.state = STATE_BOTTOM; + this.velocity = '0 0 0'; + setthink(this, func_null); + this.nextthink = 0; + if (this.health) + this.takedamage = DAMAGE_YES; // can be shot again +} + +void button_use(entity this, entity actor, entity trigger) +{ + if(this.active != ACTIVE_ACTIVE) + return; + + this.enemy = actor; + button_fire(this); +} + +void button_touch(entity this, entity toucher) +{ + if (!toucher) + return; + if (!toucher.iscreature) + return; + if(toucher.velocity * this.movedir < 0) + return; + this.enemy = toucher; + if (toucher.owner) + this.enemy = toucher.owner; + button_fire (this); +} + +void button_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) +{ + if(this.spawnflags & NOSPLASH) + if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH)) + return; + if (this.spawnflags & BUTTON_DONTACCUMULATEDMG) + { + if (this.health <= damage) + { + this.enemy = attacker; + button_fire(this); + } + } + else + { + this.health = this.health - damage; + if (this.health <= 0) + { + this.enemy = attacker; + button_fire(this); + } + } +} + + +/*QUAKED spawnfunc_func_button (0 .5 .8) ? +When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again. + +"angle" determines the opening direction +"target" all entities with a matching targetname will be used +"speed" override the default 40 speed +"wait" override the default 1 second wait (-1 = never return) +"lip" override the default 4 pixel lip remaining at end of move +"health" if set, the button must be killed instead of touched. If set to -1, the button will fire on ANY attack, even damageless ones like the InstaGib laser +"noise" sound that is played when the button is activated +*/ +spawnfunc(func_button) +{ + SetMovedir(this); + + if (!InitMovingBrushTrigger(this)) + return; + this.effects |= EF_LOWPRECISION; + + setblocked(this, button_blocked); + this.use = button_use; + +// if (this.health == 0) // all buttons are now shootable +// this.health = 10; + if (this.health) + { + this.max_health = this.health; + this.event_damage = button_damage; + this.takedamage = DAMAGE_YES; + } + else + settouch(this, button_touch); + + if (!this.speed) + this.speed = 40; + if (!this.wait) + this.wait = 1; + if (!this.lip) + this.lip = 4; + + if(this.noise != "") + precache_sound(this.noise); + + this.active = ACTIVE_ACTIVE; + + this.pos1 = this.origin; + this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip); + this.flags |= FL_NOTARGET; + + this.reset = button_reset; + + button_reset(this); +} +#endif diff --git a/qcsrc/common/mapobjects/func/button.qh b/qcsrc/common/mapobjects/func/button.qh new file mode 100644 index 000000000..86a0fc918 --- /dev/null +++ b/qcsrc/common/mapobjects/func/button.qh @@ -0,0 +1,4 @@ +#pragma once + + +const int BUTTON_DONTACCUMULATEDMG = BIT(7); diff --git a/qcsrc/common/mapobjects/func/conveyor.qc b/qcsrc/common/mapobjects/func/conveyor.qc new file mode 100644 index 000000000..9ad326cfa --- /dev/null +++ b/qcsrc/common/mapobjects/func/conveyor.qc @@ -0,0 +1,178 @@ +#include "conveyor.qh" +REGISTER_NET_LINKED(ENT_CLIENT_CONVEYOR) + +void conveyor_think(entity this) +{ +#ifdef CSQC + // TODO: check if this is what is causing the glitchiness when switching between them + float dt = time - this.move_time; + this.move_time = time; + if(dt <= 0) { return; } +#endif + + // set myself as current conveyor where possible + IL_EACH(g_conveyed, it.conveyor == this, + { + it.conveyor = NULL; + IL_REMOVE(g_conveyed, it); + }); + + if(this.active == ACTIVE_ACTIVE) + { + FOREACH_ENTITY_RADIUS((this.absmin + this.absmax) * 0.5, vlen(this.absmax - this.absmin) * 0.5 + 1, it.conveyor.active == ACTIVE_NOT && isPushable(it), + { + vector emin = it.absmin; + vector emax = it.absmax; + if(this.solid == SOLID_BSP) + { + emin -= '1 1 1'; + emax += '1 1 1'; + } + if(boxesoverlap(emin, emax, this.absmin, this.absmax)) // quick + if(WarpZoneLib_BoxTouchesBrush(emin, emax, this, it)) // accurate + { + if(!it.conveyor) + IL_PUSH(g_conveyed, it); + it.conveyor = this; + } + }); + + IL_EACH(g_conveyed, it.conveyor == this, + { + if(IS_CLIENT(it)) // doing it via velocity has quite some advantages + continue; // done in SV_PlayerPhysics continue; + + setorigin(it, it.origin + this.movedir * PHYS_INPUT_FRAMETIME); + move_out_of_solid(it); +#ifdef SVQC + UpdateCSQCProjectile(it); +#endif + /* + // stupid conveyor code + tracebox(it.origin, it.mins, it.maxs, it.origin + this.movedir * sys_frametime, MOVE_NORMAL, it); + if(trace_fraction > 0) + setorigin(it, trace_endpos); + */ + }); + } + +#ifdef SVQC + this.nextthink = time; +#endif +} + +#ifdef SVQC + +bool conveyor_send(entity this, entity to, int sendflags) +{ + WriteHeader(MSG_ENTITY, ENT_CLIENT_CONVEYOR); + WriteByte(MSG_ENTITY, sendflags); + + if(sendflags & SF_TRIGGER_INIT) + { + WriteByte(MSG_ENTITY, this.warpzone_isboxy); + WriteVector(MSG_ENTITY, this.origin); + + WriteVector(MSG_ENTITY, this.mins); + WriteVector(MSG_ENTITY, this.maxs); + + WriteVector(MSG_ENTITY, this.movedir); + + WriteByte(MSG_ENTITY, this.speed); + WriteByte(MSG_ENTITY, this.active); + + WriteString(MSG_ENTITY, this.targetname); + WriteString(MSG_ENTITY, this.target); + } + + if(sendflags & SF_TRIGGER_UPDATE) + WriteByte(MSG_ENTITY, this.active); + + return true; +} + +void conveyor_init(entity this) +{ + if (!this.speed) this.speed = 200; + this.movedir *= this.speed; + setthink(this, conveyor_think); + this.nextthink = time; + this.setactive = generic_netlinked_setactive; + IFTARGETED + { + // backwards compatibility + this.use = generic_netlinked_legacy_use; + } + this.reset = generic_netlinked_reset; + this.reset(this); + + FixSize(this); + + Net_LinkEntity(this, 0, false, conveyor_send); + + this.SendFlags |= SF_TRIGGER_INIT; +} + +spawnfunc(trigger_conveyor) +{ + SetMovedir(this); + EXACTTRIGGER_INIT; + conveyor_init(this); +} + +spawnfunc(func_conveyor) +{ + SetMovedir(this); + InitMovingBrushTrigger(this); + set_movetype(this, MOVETYPE_NONE); + conveyor_init(this); +} + +#elif defined(CSQC) + +void conveyor_draw(entity this) { conveyor_think(this); } + +void conveyor_init(entity this, bool isnew) +{ + if(isnew) + IL_PUSH(g_drawables, this); + this.draw = conveyor_draw; + this.drawmask = MASK_NORMAL; + + set_movetype(this, MOVETYPE_NONE); + this.model = ""; + this.solid = SOLID_TRIGGER; + this.move_time = time; +} + +NET_HANDLE(ENT_CLIENT_CONVEYOR, bool isnew) +{ + int sendflags = ReadByte(); + + if(sendflags & SF_TRIGGER_INIT) + { + this.warpzone_isboxy = ReadByte(); + this.origin = ReadVector(); + setorigin(this, this.origin); + + this.mins = ReadVector(); + this.maxs = ReadVector(); + setsize(this, this.mins, this.maxs); + + this.movedir = ReadVector(); + + this.speed = ReadByte(); + this.active = ReadByte(); + + this.targetname = strzone(ReadString()); + this.target = strzone(ReadString()); + + conveyor_init(this, isnew); + } + + if(sendflags & SF_TRIGGER_UPDATE) + this.active = ReadByte(); + + return true; +} +#endif diff --git a/qcsrc/common/mapobjects/func/conveyor.qh b/qcsrc/common/mapobjects/func/conveyor.qh new file mode 100644 index 000000000..22b674ff3 --- /dev/null +++ b/qcsrc/common/mapobjects/func/conveyor.qh @@ -0,0 +1,5 @@ +#pragma once +#include "../defs.qh" + +IntrusiveList g_conveyed; +STATIC_INIT(g_conveyed) { g_conveyed = IL_NEW(); } diff --git a/qcsrc/common/mapobjects/func/door.qc b/qcsrc/common/mapobjects/func/door.qc new file mode 100644 index 000000000..c19041aa0 --- /dev/null +++ b/qcsrc/common/mapobjects/func/door.qc @@ -0,0 +1,801 @@ +#include "door.qh" +#include "door_rotating.qh" +/* + +Doors are similar to buttons, but can spawn a fat trigger field around them +to open without a touch, and they link together to form simultanious +double/quad doors. + +Door.owner is the master door. If there is only one door, it points to itself. +If multiple doors, all will point to a single one. + +Door.enemy chains from the master door through all doors linked in the chain. + +*/ + + +/* +============================================================================= + +THINK FUNCTIONS + +============================================================================= +*/ + +void door_go_down(entity this); +void door_go_up(entity this, entity actor, entity trigger); + +void door_blocked(entity this, entity blocker) +{ + if((this.spawnflags & DOOR_CRUSH) +#ifdef SVQC + && (blocker.takedamage != DAMAGE_NO) +#elif defined(CSQC) + && !IS_DEAD(blocker) +#endif + ) + { // KIll Kill Kill!! +#ifdef SVQC + Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); +#endif + } + else + { +#ifdef SVQC + if((this.dmg) && (blocker.takedamage == DAMAGE_YES)) // Shall we bite? + Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); +#endif + + // don't change direction for dead or dying stuff + if(IS_DEAD(blocker) +#ifdef SVQC + && (blocker.takedamage == DAMAGE_NO) +#endif + ) + { + if (this.wait >= 0) + { + if (this.state == STATE_DOWN) + { + if (this.classname == "door") + door_go_up(this, NULL, NULL); + else + door_rotating_go_up(this, blocker); + } + else + { + if (this.classname == "door") + door_go_down(this); + else + door_rotating_go_down(this); + } + } + } +#ifdef SVQC + else + { + //gib dying stuff just to make sure + if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) // Shall we bite? + Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); + } +#endif + } +} + +void door_hit_top(entity this) +{ + if (this.noise1 != "") + _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM); + this.state = STATE_TOP; + if (this.spawnflags & DOOR_TOGGLE) + return; // don't come down automatically + if (this.classname == "door") + { + setthink(this, door_go_down); + } else + { + setthink(this, door_rotating_go_down); + } + this.nextthink = this.ltime + this.wait; +} + +void door_hit_bottom(entity this) +{ + if (this.noise1 != "") + _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM); + this.state = STATE_BOTTOM; +} + +void door_go_down(entity this) +{ + if (this.noise2 != "") + _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM); + if (this.max_health) + { + this.takedamage = DAMAGE_YES; + this.health = this.max_health; + } + + this.state = STATE_DOWN; + SUB_CalcMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_hit_bottom); +} + +void door_go_up(entity this, entity actor, entity trigger) +{ + if (this.state == STATE_UP) + return; // already going up + + if (this.state == STATE_TOP) + { // reset top wait time + this.nextthink = this.ltime + this.wait; + return; + } + + if (this.noise2 != "") + _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM); + this.state = STATE_UP; + SUB_CalcMove (this, this.pos2, TSPEED_LINEAR, this.speed, door_hit_top); + + string oldmessage; + oldmessage = this.message; + this.message = ""; + SUB_UseTargets(this, actor, trigger); + this.message = oldmessage; +} + + +/* +============================================================================= + +ACTIVATION FUNCTIONS + +============================================================================= +*/ + +bool door_check_keys(entity door, entity player) +{ + if(door.owner) + door = door.owner; + + // no key needed + if(!door.itemkeys) + return true; + + // this door require a key + // only a player can have a key + if(!IS_PLAYER(player)) + return false; + + entity store = player; +#ifdef SVQC + store = PS(player); +#endif + int valid = (door.itemkeys & store.itemkeys); + door.itemkeys &= ~valid; // only some of the needed keys were given + + if(!door.itemkeys) + { +#ifdef SVQC + play2(player, door.noise); + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_UNLOCKED); +#endif + return true; + } + + if(!valid) + { +#ifdef SVQC + if(player.key_door_messagetime <= time) + { + play2(player, door.noise3); + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys)); + player.key_door_messagetime = time + 2; + } +#endif + return false; + } + + // door needs keys the player doesn't have +#ifdef SVQC + if(player.key_door_messagetime <= time) + { + play2(player, door.noise3); + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys)); + player.key_door_messagetime = time + 2; + } +#endif + + return false; +} + +void door_fire(entity this, entity actor, entity trigger) +{ + if (this.owner != this) + objerror (this, "door_fire: this.owner != this"); + + if (this.spawnflags & DOOR_TOGGLE) + { + if (this.state == STATE_UP || this.state == STATE_TOP) + { + entity e = this; + do { + if (e.classname == "door") { + door_go_down(e); + } else { + door_rotating_go_down(e); + } + e = e.enemy; + } while ((e != this) && (e != NULL)); + return; + } + } + +// trigger all paired doors + entity e = this; + do { + if (e.classname == "door") { + door_go_up(e, actor, trigger); + } else { + // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction + if ((e.spawnflags & DOOR_ROTATING_BIDIR) && trigger.trigger_reverse!=0 && e.lip != 666 && e.state == STATE_BOTTOM) { + e.lip = 666; // e.lip is used to remember reverse opening direction for door_rotating + e.pos2 = '0 0 0' - e.pos2; + } + // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side + if (!((e.spawnflags & DOOR_ROTATING_BIDIR) && (e.spawnflags & DOOR_ROTATING_BIDIR_IN_DOWN) && e.state == STATE_DOWN + && (((e.lip == 666) && (trigger.trigger_reverse == 0)) || ((e.lip != 666) && (trigger.trigger_reverse != 0))))) + { + door_rotating_go_up(e, trigger); + } + } + e = e.enemy; + } while ((e != this) && (e != NULL)); +} + +void door_use(entity this, entity actor, entity trigger) +{ + //dprint("door_use (model: ");dprint(this.model);dprint(")\n"); + + if (this.owner) + door_fire(this.owner, actor, trigger); +} + +void door_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) +{ + if(this.spawnflags & NOSPLASH) + if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH)) + return; + this.health = this.health - damage; + + if (this.itemkeys) + { + // don't allow opening doors through damage if keys are required + return; + } + + if (this.health <= 0) + { + this.owner.health = this.owner.max_health; + this.owner.takedamage = DAMAGE_NO; // wil be reset upon return + door_use(this.owner, NULL, NULL); + } +} + +.float door_finished; + +/* +================ +door_touch + +Prints messages +================ +*/ + +void door_touch(entity this, entity toucher) +{ + if (!IS_PLAYER(toucher)) + return; + if (this.owner.door_finished > time) + return; + + this.owner.door_finished = time + 2; + +#ifdef SVQC + if (!(this.owner.dmg) && (this.owner.message != "")) + { + if (IS_CLIENT(toucher)) + centerprint(toucher, this.owner.message); + play2(toucher, this.owner.noise); + } +#endif +} + +void door_generic_plat_blocked(entity this, entity blocker) +{ + if((this.spawnflags & DOOR_CRUSH) && (blocker.takedamage != DAMAGE_NO)) { // Kill Kill Kill!! +#ifdef SVQC + Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); +#endif + } + else + { + +#ifdef SVQC + if((this.dmg) && (blocker.takedamage == DAMAGE_YES)) // Shall we bite? + Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); +#endif + + //Dont chamge direction for dead or dying stuff + if(IS_DEAD(blocker) && (blocker.takedamage == DAMAGE_NO)) + { + if (this.wait >= 0) + { + if (this.state == STATE_DOWN) + door_rotating_go_up (this, blocker); + else + door_rotating_go_down (this); + } + } +#ifdef SVQC + else + { + //gib dying stuff just to make sure + if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) // Shall we bite? + Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); + } +#endif + } +} + +/* +========================================= +door trigger + +Spawned if a door lacks a real activator +========================================= +*/ + +void door_trigger_touch(entity this, entity toucher) +{ + if (toucher.health < 1) +#ifdef SVQC + if (!((toucher.iscreature || (toucher.flags & FL_PROJECTILE)) && !IS_DEAD(toucher))) +#elif defined(CSQC) + if(!((IS_CLIENT(toucher) || toucher.classname == "csqcprojectile") && !IS_DEAD(toucher))) +#endif + return; + + if (time < this.door_finished) + return; + + // check if door is locked + if (!door_check_keys(this, toucher)) + return; + + this.door_finished = time + 1; + + door_use(this.owner, toucher, NULL); +} + +void door_spawnfield(entity this, vector fmins, vector fmaxs) +{ + entity trigger; + vector t1 = fmins, t2 = fmaxs; + + trigger = new(doortriggerfield); + set_movetype(trigger, MOVETYPE_NONE); + trigger.solid = SOLID_TRIGGER; + trigger.owner = this; +#ifdef SVQC + settouch(trigger, door_trigger_touch); +#endif + + setsize (trigger, t1 - '60 60 8', t2 + '60 60 8'); +} + + +/* +============= +LinkDoors + + +============= +*/ + +entity LinkDoors_nextent(entity cur, entity near, entity pass) +{ + while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & DOOR_DONT_LINK) || cur.enemy)) + { + } + return cur; +} + +bool LinkDoors_isconnected(entity e1, entity e2, entity pass) +{ + float DELTA = 4; + if((e1.absmin_x > e2.absmax_x + DELTA) + || (e1.absmin_y > e2.absmax_y + DELTA) + || (e1.absmin_z > e2.absmax_z + DELTA) + || (e2.absmin_x > e1.absmax_x + DELTA) + || (e2.absmin_y > e1.absmax_y + DELTA) + || (e2.absmin_z > e1.absmax_z + DELTA) + ) { return false; } + return true; +} + +#ifdef SVQC +void door_link(); +#endif +void LinkDoors(entity this) +{ + entity t; + vector cmins, cmaxs; + +#ifdef SVQC + door_link(); +#endif + + if (this.enemy) + return; // already linked by another door + if (this.spawnflags & DOOR_DONT_LINK) + { + this.owner = this.enemy = this; + + if (this.health) + return; + IFTARGETED + return; + if (this.items) + return; + + door_spawnfield(this, this.absmin, this.absmax); + + return; // don't want to link this door + } + + FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this); + + // set owner, and make a loop of the chain + LOG_TRACE("LinkDoors: linking doors:"); + for(t = this; ; t = t.enemy) + { + LOG_TRACE(" ", etos(t)); + t.owner = this; + if(t.enemy == NULL) + { + t.enemy = this; + break; + } + } + LOG_TRACE(""); + + // collect health, targetname, message, size + cmins = this.absmin; + cmaxs = this.absmax; + for(t = this; ; t = t.enemy) + { + if(t.health && !this.health) + this.health = t.health; + if((t.targetname != "") && (this.targetname == "")) + this.targetname = t.targetname; + if((t.message != "") && (this.message == "")) + this.message = t.message; + if (t.absmin_x < cmins_x) + cmins_x = t.absmin_x; + if (t.absmin_y < cmins_y) + cmins_y = t.absmin_y; + if (t.absmin_z < cmins_z) + cmins_z = t.absmin_z; + if (t.absmax_x > cmaxs_x) + cmaxs_x = t.absmax_x; + if (t.absmax_y > cmaxs_y) + cmaxs_y = t.absmax_y; + if (t.absmax_z > cmaxs_z) + cmaxs_z = t.absmax_z; + if(t.enemy == this) + break; + } + + // distribute health, targetname, message + for(t = this; t; t = t.enemy) + { + t.health = this.health; + t.targetname = this.targetname; + t.message = this.message; + if(t.enemy == this) + break; + } + + // shootable, or triggered doors just needed the owner/enemy links, + // they don't spawn a field + + if (this.health) + return; + IFTARGETED + return; + if (this.items) + return; + + door_spawnfield(this, cmins, cmaxs); +} + +REGISTER_NET_LINKED(ENT_CLIENT_DOOR) + +#ifdef SVQC +/*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE +if two doors touch, they are assumed to be connected and operate as a unit. + +TOGGLE causes the door to wait in both the start and end states for a trigger event. + +START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). + +GOLD_KEY causes the door to open only if the activator holds a gold key. + +SILVER_KEY causes the door to open only if the activator holds a silver key. + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"sounds" +0) no sound +1) stone +2) base +3) stone chain +4) screechy metal +FIXME: only one sound set available at the time being + +*/ + +float door_send(entity this, entity to, float sf) +{ + WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR); + WriteByte(MSG_ENTITY, sf); + + if(sf & SF_TRIGGER_INIT) + { + WriteString(MSG_ENTITY, this.classname); + WriteByte(MSG_ENTITY, this.spawnflags); + + WriteString(MSG_ENTITY, this.model); + + trigger_common_write(this, true); + + WriteVector(MSG_ENTITY, this.pos1); + WriteVector(MSG_ENTITY, this.pos2); + + WriteVector(MSG_ENTITY, this.size); + + WriteShort(MSG_ENTITY, this.wait); + WriteShort(MSG_ENTITY, this.speed); + WriteByte(MSG_ENTITY, this.lip); + WriteByte(MSG_ENTITY, this.state); + WriteCoord(MSG_ENTITY, this.ltime); + } + + if(sf & SF_TRIGGER_RESET) + { + // client makes use of this, we do not + } + + if(sf & SF_TRIGGER_UPDATE) + { + WriteVector(MSG_ENTITY, this.origin); + + WriteVector(MSG_ENTITY, this.pos1); + WriteVector(MSG_ENTITY, this.pos2); + } + + return true; +} + +void door_link() +{ + // set size now, as everything is loaded + //FixSize(this); + //Net_LinkEntity(this, false, 0, door_send); +} +#endif + +void door_init_startopen(entity this) +{ + setorigin(this, this.pos2); + this.pos2 = this.pos1; + this.pos1 = this.origin; + +#ifdef SVQC + this.SendFlags |= SF_TRIGGER_UPDATE; +#endif +} + +void door_reset(entity this) +{ + setorigin(this, this.pos1); + this.velocity = '0 0 0'; + this.state = STATE_BOTTOM; + setthink(this, func_null); + this.nextthink = 0; + +#ifdef SVQC + this.SendFlags |= SF_TRIGGER_RESET; +#endif +} + +#ifdef SVQC + +// common code for func_door and func_door_rotating spawnfuncs +void door_init_shared(entity this) +{ + this.max_health = this.health; + + // unlock sound + if(this.noise == "") + { + this.noise = "misc/talk.wav"; + } + // door still locked sound + if(this.noise3 == "") + { + this.noise3 = "misc/talk.wav"; + } + precache_sound(this.noise); + precache_sound(this.noise3); + + if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message == "")) + { + this.message = "was squished"; + } + if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message2 == "")) + { + this.message2 = "was squished by"; + } + + // TODO: other soundpacks + if (this.sounds > 0) + { + this.noise2 = "plats/medplat1.wav"; + this.noise1 = "plats/medplat2.wav"; + } + + // sound when door stops moving + if(this.noise1 && this.noise1 != "") + { + precache_sound(this.noise1); + } + // sound when door is moving + if(this.noise2 && this.noise2 != "") + { + precache_sound(this.noise2); + } + + if (!this.wait) + { + this.wait = 3; + } + if (!this.lip) + { + this.lip = 8; + } + + this.state = STATE_BOTTOM; + + if (this.health) + { + //this.canteamdamage = true; // TODO + this.takedamage = DAMAGE_YES; + this.event_damage = door_damage; + } + + if (this.items) + { + this.wait = -1; + } +} + +// spawnflags require key (for now only func_door) +spawnfunc(func_door) +{ + // Quake 1 keys compatibility + if (this.spawnflags & SPAWNFLAGS_GOLD_KEY) + this.itemkeys |= ITEM_KEY_BIT(0); + if (this.spawnflags & SPAWNFLAGS_SILVER_KEY) + this.itemkeys |= ITEM_KEY_BIT(1); + + SetMovedir(this); + + if (!InitMovingBrushTrigger(this)) + return; + this.effects |= EF_LOWPRECISION; + this.classname = "door"; + + setblocked(this, door_blocked); + this.use = door_use; + + this.pos1 = this.origin; + this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip); + + if(this.spawnflags & DOOR_NONSOLID) + this.solid = SOLID_NOT; + +// DOOR_START_OPEN is to allow an entity to be lighted in the closed position +// but spawn in the open position + if (this.spawnflags & DOOR_START_OPEN) + InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION); + + door_init_shared(this); + + if (!this.speed) + { + this.speed = 100; + } + + settouch(this, door_touch); + +// LinkDoors can't be done until all of the doors have been spawned, so +// the sizes can be detected properly. + InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS); + + this.reset = door_reset; +} + +#elif defined(CSQC) + +NET_HANDLE(ENT_CLIENT_DOOR, bool isnew) +{ + int sf = ReadByte(); + + if(sf & SF_TRIGGER_INIT) + { + this.classname = strzone(ReadString()); + this.spawnflags = ReadByte(); + + this.mdl = strzone(ReadString()); + _setmodel(this, this.mdl); + + trigger_common_read(this, true); + + this.pos1 = ReadVector(); + this.pos2 = ReadVector(); + + this.size = ReadVector(); + + this.wait = ReadShort(); + this.speed = ReadShort(); + this.lip = ReadByte(); + this.state = ReadByte(); + this.ltime = ReadCoord(); + + this.solid = SOLID_BSP; + set_movetype(this, MOVETYPE_PUSH); + this.use = door_use; + + LinkDoors(this); + + if(this.spawnflags & DOOR_START_OPEN) + door_init_startopen(this); + + this.move_time = time; + set_movetype(this, MOVETYPE_PUSH); + } + + if(sf & SF_TRIGGER_RESET) + { + door_reset(this); + } + + if(sf & SF_TRIGGER_UPDATE) + { + this.origin = ReadVector(); + setorigin(this, this.origin); + + this.pos1 = ReadVector(); + this.pos2 = ReadVector(); + } + return true; +} + +#endif diff --git a/qcsrc/common/mapobjects/func/door.qh b/qcsrc/common/mapobjects/func/door.qh new file mode 100644 index 000000000..181de8b7b --- /dev/null +++ b/qcsrc/common/mapobjects/func/door.qh @@ -0,0 +1,18 @@ +#pragma once + + +const int DOOR_START_OPEN = BIT(0); +const int DOOR_DONT_LINK = BIT(2); +const int SPAWNFLAGS_GOLD_KEY = BIT(3); // Quake 1 compat, can only be used with func_door! +const int SPAWNFLAGS_SILVER_KEY = BIT(4); // Quake 1 compat, can only be used with func_door! +const int DOOR_TOGGLE = BIT(5); + +const int DOOR_NONSOLID = BIT(10); +const int DOOR_CRUSH = BIT(11); // can't use CRUSH cause that is the same as DOOR_DONT_LINK + + +#ifdef CSQC +// stuff for preload + +.float door_finished; +#endif diff --git a/qcsrc/common/mapobjects/func/door_rotating.qc b/qcsrc/common/mapobjects/func/door_rotating.qc new file mode 100644 index 000000000..41fd05e57 --- /dev/null +++ b/qcsrc/common/mapobjects/func/door_rotating.qc @@ -0,0 +1,159 @@ +#include "door_rotating.qh" +/*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS +if two doors touch, they are assumed to be connected and operate as a unit. + +TOGGLE causes the door to wait in both the start and end states for a trigger event. + +BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor. +The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction +must have set trigger_reverse to 1. +BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side. + +START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors). + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the destination angle for opening. negative values reverse the direction. +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (2 default) +"sounds" +0) no sound +1) stone +2) base +3) stone chain +4) screechy metal +FIXME: only one sound set available at the time being +*/ + +#ifdef GAMEQC +void door_rotating_hit_top(entity this) +{ + if (this.noise1 != "") + _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM); + this.state = STATE_TOP; + if (this.spawnflags & DOOR_TOGGLE) + return; // don't come down automatically + setthink(this, door_rotating_go_down); + this.nextthink = this.ltime + this.wait; +} + +void door_rotating_hit_bottom(entity this) +{ + if (this.noise1 != "") + _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM); + if (this.lip==666) // this.lip is used to remember reverse opening direction for door_rotating + { + this.pos2 = '0 0 0' - this.pos2; + this.lip = 0; + } + this.state = STATE_BOTTOM; +} + +void door_rotating_go_down(entity this) +{ + if (this.noise2 != "") + _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM); + if (this.max_health) + { + this.takedamage = DAMAGE_YES; + this.health = this.max_health; + } + + this.state = STATE_DOWN; + SUB_CalcAngleMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_rotating_hit_bottom); +} + +void door_rotating_go_up(entity this, entity oth) +{ + if (this.state == STATE_UP) + return; // already going up + + if (this.state == STATE_TOP) + { // reset top wait time + this.nextthink = this.ltime + this.wait; + return; + } + if (this.noise2 != "") + _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM); + this.state = STATE_UP; + SUB_CalcAngleMove (this, this.pos2, TSPEED_LINEAR, this.speed, door_rotating_hit_top); + + string oldmessage; + oldmessage = this.message; + this.message = ""; + SUB_UseTargets(this, NULL, oth); // TODO: is oth needed here? + this.message = oldmessage; +} +#endif + +#ifdef SVQC +void door_rotating_reset(entity this) +{ + this.angles = this.pos1; + this.avelocity = '0 0 0'; + this.state = STATE_BOTTOM; + setthink(this, func_null); + this.nextthink = 0; +} + +void door_rotating_init_startopen(entity this) +{ + this.angles = this.movedir; + this.pos2 = '0 0 0'; + this.pos1 = this.movedir; +} + +spawnfunc(func_door_rotating) +{ + //if (!this.deathtype) // map makers can override this + // this.deathtype = " got in the way"; + + // I abuse "movedir" for denoting the axis for now + if (this.spawnflags & DOOR_ROTATING_XAXIS) + this.movedir = '0 0 1'; + else if (this.spawnflags & DOOR_ROTATING_YAXIS) + this.movedir = '1 0 0'; + else // Z + this.movedir = '0 1 0'; + + if (this.angles_y==0) this.angles_y = 90; + + this.movedir = this.movedir * this.angles_y; + this.angles = '0 0 0'; + + this.avelocity = this.movedir; + if (!InitMovingBrushTrigger(this)) + return; + this.velocity = '0 0 0'; + //this.effects |= EF_LOWPRECISION; + this.classname = "door_rotating"; + + setblocked(this, door_blocked); + this.use = door_use; + + this.pos1 = '0 0 0'; + this.pos2 = this.movedir; + +// DOOR_START_OPEN is to allow an entity to be lighted in the closed position +// but spawn in the open position + if (this.spawnflags & DOOR_START_OPEN) + InitializeEntity(this, door_rotating_init_startopen, INITPRIO_SETLOCATION); + + door_init_shared(this); + if (!this.speed) + { + this.speed = 50; + } + this.lip = 0; // this.lip is used to remember reverse opening direction for door_rotating + + settouch(this, door_touch); + +// LinkDoors can't be done until all of the doors have been spawned, so +// the sizes can be detected properly. + InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS); + + this.reset = door_rotating_reset; +} +#endif diff --git a/qcsrc/common/mapobjects/func/door_rotating.qh b/qcsrc/common/mapobjects/func/door_rotating.qh new file mode 100644 index 000000000..5ff572859 --- /dev/null +++ b/qcsrc/common/mapobjects/func/door_rotating.qh @@ -0,0 +1,14 @@ +#pragma once + + +const int DOOR_ROTATING_BIDIR = BIT(1); +const int DOOR_ROTATING_BIDIR_IN_DOWN = BIT(3); + +const int DOOR_ROTATING_XAXIS = BIT(6); +const int DOOR_ROTATING_YAXIS = BIT(7); + + +#ifdef GAMEQC +void door_rotating_go_down(entity this); +void door_rotating_go_up(entity this, entity oth); +#endif diff --git a/qcsrc/common/mapobjects/func/door_secret.qc b/qcsrc/common/mapobjects/func/door_secret.qc new file mode 100644 index 000000000..78e0dd64e --- /dev/null +++ b/qcsrc/common/mapobjects/func/door_secret.qc @@ -0,0 +1,266 @@ +#include "door_secret.qh" +#ifdef SVQC +void fd_secret_move1(entity this); +void fd_secret_move2(entity this); +void fd_secret_move3(entity this); +void fd_secret_move4(entity this); +void fd_secret_move5(entity this); +void fd_secret_move6(entity this); +void fd_secret_done(entity this); + +void fd_secret_use(entity this, entity actor, entity trigger) +{ + float temp; + string message_save; + + this.health = 10000; + if(!this.bot_attack) + IL_PUSH(g_bot_targets, this); + this.bot_attack = true; + + // exit if still moving around... + if (this.origin != this.oldorigin) + return; + + message_save = this.message; + this.message = ""; // no more message + SUB_UseTargets(this, actor, trigger); // fire all targets / killtargets + this.message = message_save; + + this.velocity = '0 0 0'; + + // Make a sound, wait a little... + + if (this.noise1 != "") + _sound(this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM); + this.nextthink = this.ltime + 0.1; + + temp = 1 - (this.spawnflags & DOOR_SECRET_1ST_LEFT); // 1 or -1 + makevectors(this.mangle); + + if (!this.t_width) + { + if (this.spawnflags & DOOR_SECRET_1ST_DOWN) + this.t_width = fabs(v_up * this.size); + else + this.t_width = fabs(v_right * this.size); + } + + if (!this.t_length) + this.t_length = fabs(v_forward * this.size); + + if (this.spawnflags & DOOR_SECRET_1ST_DOWN) + this.dest1 = this.origin - v_up * this.t_width; + else + this.dest1 = this.origin + v_right * (this.t_width * temp); + + this.dest2 = this.dest1 + v_forward * this.t_length; + SUB_CalcMove(this, this.dest1, TSPEED_LINEAR, this.speed, fd_secret_move1); + if (this.noise2 != "") + _sound(this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM); +} + +void fd_secret_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) +{ + fd_secret_use(this, NULL, NULL); +} + +// Wait after first movement... +void fd_secret_move1(entity this) +{ + this.nextthink = this.ltime + 1.0; + setthink(this, fd_secret_move2); + if (this.noise3 != "") + _sound(this, CH_TRIGGER_SINGLE, this.noise3, VOL_BASE, ATTEN_NORM); +} + +// Start moving sideways w/sound... +void fd_secret_move2(entity this) +{ + if (this.noise2 != "") + _sound(this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM); + SUB_CalcMove(this, this.dest2, TSPEED_LINEAR, this.speed, fd_secret_move3); +} + +// Wait here until time to go back... +void fd_secret_move3(entity this) +{ + if (this.noise3 != "") + _sound(this, CH_TRIGGER_SINGLE, this.noise3, VOL_BASE, ATTEN_NORM); + if (!(this.spawnflags & DOOR_SECRET_OPEN_ONCE) && this.wait >= 0) + { + this.nextthink = this.ltime + this.wait; + setthink(this, fd_secret_move4); + } +} + +// Move backward... +void fd_secret_move4(entity this) +{ + if (this.noise2 != "") + _sound(this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM); + SUB_CalcMove(this, this.dest1, TSPEED_LINEAR, this.speed, fd_secret_move5); +} + +// Wait 1 second... +void fd_secret_move5(entity this) +{ + this.nextthink = this.ltime + 1.0; + setthink(this, fd_secret_move6); + if (this.noise3 != "") + _sound(this, CH_TRIGGER_SINGLE, this.noise3, VOL_BASE, ATTEN_NORM); +} + +void fd_secret_move6(entity this) +{ + if (this.noise2 != "") + _sound(this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM); + SUB_CalcMove(this, this.oldorigin, TSPEED_LINEAR, this.speed, fd_secret_done); +} + +void fd_secret_done(entity this) +{ + if (this.spawnflags&DOOR_SECRET_YES_SHOOT) + { + this.health = 10000; + this.takedamage = DAMAGE_YES; + //this.th_pain = fd_secret_use; + } + if (this.noise3 != "") + _sound(this, CH_TRIGGER_SINGLE, this.noise3, VOL_BASE, ATTEN_NORM); +} + +.float door_finished; + +void secret_blocked(entity this, entity blocker) +{ + if (time < this.door_finished) + return; + this.door_finished = time + 0.5; + //T_Damage (other, this, this, this.dmg, this.dmg, this.deathtype, DT_IMPACT, (this.absmin + this.absmax) * 0.5, '0 0 0', Obituary_Generic); +} + +/* +============== +secret_touch + +Prints messages +================ +*/ +void secret_touch(entity this, entity toucher) +{ + if (!toucher.iscreature) + return; + if (this.door_finished > time) + return; + + this.door_finished = time + 2; + + if (this.message) + { + if (IS_CLIENT(toucher)) + centerprint(toucher, this.message); + play2(toucher, this.noise); + } +} + +void secret_reset(entity this) +{ + if (this.spawnflags & DOOR_SECRET_YES_SHOOT) + { + this.health = 10000; + this.takedamage = DAMAGE_YES; + } + setorigin(this, this.oldorigin); + setthink(this, func_null); + this.nextthink = 0; +} + +/*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot +Basic secret door. Slides back, then to the side. Angle determines direction. +wait = # of seconds before coming back +1st_left = 1st move is left of arrow +1st_down = 1st move is down from arrow +always_shoot = even if targeted, keep shootable +t_width = override WIDTH to move back (or height if going down) +t_length = override LENGTH to move sideways +"dmg" damage to inflict when blocked (2 default) + +If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage. +"sounds" +1) medieval +2) metal +3) base +*/ + +spawnfunc(func_door_secret) +{ + /*if (!this.deathtype) // map makers can override this + this.deathtype = " got in the way";*/ + + if (!this.dmg) + { + this.dmg = 2; + } + + // Magic formula... + this.mangle = this.angles; + this.angles = '0 0 0'; + this.classname = "door"; + if (!InitMovingBrushTrigger(this)) return; + this.effects |= EF_LOWPRECISION; + + // TODO: other soundpacks + if (this.sounds > 0) + { + this.noise1 = "plats/medplat1.wav"; + this.noise2 = "plats/medplat1.wav"; + this.noise3 = "plats/medplat2.wav"; + } + + // sound on touch + if (this.noise == "") + { + this.noise = "misc/talk.wav"; + } + precache_sound(this.noise); + // sound while moving backwards + if (this.noise1 && this.noise1 != "") + { + precache_sound(this.noise1); + } + // sound while moving sideways + if (this.noise2 && this.noise2 != "") + { + precache_sound(this.noise2); + } + // sound when door stops moving + if (this.noise3 && this.noise3 != "") + { + precache_sound(this.noise3); + } + + settouch(this, secret_touch); + setblocked(this, secret_blocked); + this.speed = 50; + this.use = fd_secret_use; + IFTARGETED + { + } + else + this.spawnflags |= DOOR_SECRET_YES_SHOOT; + + if (this.spawnflags & DOOR_SECRET_YES_SHOOT) + { + //this.canteamdamage = true; // TODO + this.health = 10000; + this.takedamage = DAMAGE_YES; + this.event_damage = fd_secret_damage; + } + this.oldorigin = this.origin; + if (!this.wait) this.wait = 5; // seconds before closing + + this.reset = secret_reset; + this.reset(this); +} +#endif diff --git a/qcsrc/common/mapobjects/func/door_secret.qh b/qcsrc/common/mapobjects/func/door_secret.qh new file mode 100644 index 000000000..ee575bc42 --- /dev/null +++ b/qcsrc/common/mapobjects/func/door_secret.qh @@ -0,0 +1,8 @@ +#pragma once + + +const int DOOR_SECRET_OPEN_ONCE = BIT(0); // stays open - LEGACY, set wait to -1 instead +const int DOOR_SECRET_1ST_LEFT = BIT(1); // 1st move is left of arrow +const int DOOR_SECRET_1ST_DOWN = BIT(2); // 1st move is down from arrow +const int DOOR_SECRET_NO_SHOOT = BIT(3); // only opened by trigger +const int DOOR_SECRET_YES_SHOOT = BIT(4); // shootable even if targeted diff --git a/qcsrc/common/mapobjects/func/fourier.qc b/qcsrc/common/mapobjects/func/fourier.qc new file mode 100644 index 000000000..28e0f0f7c --- /dev/null +++ b/qcsrc/common/mapobjects/func/fourier.qc @@ -0,0 +1,89 @@ +#include "fourier.qh" +#ifdef SVQC +/*QUAKED spawnfunc_func_fourier (0 .5 .8) ? +Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions. +netname: list of quadruples, separated by spaces; note that phase 0 represents a sine wave, and phase 0.25 a cosine wave (by default, it uses 1 0 0 0 1, to match func_bobbing's defaults +speed: how long one cycle of frequency multiplier 1 in seconds (default 4) +height: amplitude modifier (default 32) +phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0) +noise: path/name of looping .wav file to play. +dmg: Do this mutch dmg every .dmgtime intervall when blocked +dmgtime: See above. +*/ + +void func_fourier_controller_think(entity this) +{ + vector v; + float n, i, t; + + this.nextthink = time + 0.1; + if(this.owner.active != ACTIVE_ACTIVE) + { + this.owner.velocity = '0 0 0'; + return; + } + + + n = floor((tokenize_console(this.owner.netname)) / 5); + t = this.nextthink * this.owner.cnt + this.owner.phase * 360; + + v = this.owner.destvec; + + for(i = 0; i < n; ++i) + { + makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0'); + v = v + ('1 0 0' * stof(argv(i*5+2)) + '0 1 0' * stof(argv(i*5+3)) + '0 0 1' * stof(argv(i*5+4))) * this.owner.height * v_forward_y; + } + + if(this.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed + // * 10 so it will arrive in 0.1 sec + this.owner.velocity = (v - this.owner.origin) * 10; +} + +spawnfunc(func_fourier) +{ + entity controller; + if (this.noise != "") + { + precache_sound(this.noise); + soundto(MSG_INIT, this, CH_TRIGGER_SINGLE, this.noise, VOL_BASE, ATTEN_IDLE); + } + + if (!this.speed) + this.speed = 4; + if (!this.height) + this.height = 32; + this.destvec = this.origin; + this.cnt = 360 / this.speed; + + setblocked(this, generic_plat_blocked); + if(this.dmg && (this.message == "")) + this.message = " was squished"; + if(this.dmg && (this.message2 == "")) + this.message2 = "was squished by"; + if(this.dmg && (!this.dmgtime)) + this.dmgtime = 0.25; + this.dmgtime2 = time; + + if(this.netname == "") + this.netname = "1 0 0 0 1"; + + if (!InitMovingBrushTrigger(this)) + return; + + this.active = ACTIVE_ACTIVE; + + // wait for targets to spawn + controller = new(func_fourier_controller); + controller.owner = this; + controller.nextthink = time + 1; + setthink(controller, func_fourier_controller_think); + this.nextthink = this.ltime + 999999999; + setthink(this, SUB_NullThink); // for PushMove + + // Savage: Reduce bandwith, critical on e.g. nexdm02 + this.effects |= EF_LOWPRECISION; + + // TODO make a reset function for this one +} +#endif diff --git a/qcsrc/common/mapobjects/func/fourier.qh b/qcsrc/common/mapobjects/func/fourier.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/func/fourier.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/func/ladder.qc b/qcsrc/common/mapobjects/func/ladder.qc new file mode 100644 index 000000000..020ecca08 --- /dev/null +++ b/qcsrc/common/mapobjects/func/ladder.qc @@ -0,0 +1,148 @@ +#include "ladder.qh" +REGISTER_NET_LINKED(ENT_CLIENT_LADDER) + +void func_ladder_touch(entity this, entity toucher) +{ +#ifdef SVQC + if (!toucher.iscreature) + return; + if(IS_VEHICLE(toucher)) + return; +#elif defined(CSQC) + if(!toucher.isplayermodel) + return; +#endif + + EXACTTRIGGER_TOUCH(this, toucher); + + toucher.ladder_time = time + 0.1; + toucher.ladder_entity = this; +} + +#ifdef SVQC +bool func_ladder_send(entity this, entity to, int sf) +{ + WriteHeader(MSG_ENTITY, ENT_CLIENT_LADDER); + + WriteString(MSG_ENTITY, this.classname); + WriteByte(MSG_ENTITY, this.skin); + WriteCoord(MSG_ENTITY, this.speed); + + trigger_common_write(this, false); + + return true; +} + +void func_ladder_link(entity this) +{ + trigger_link(this, func_ladder_send); + //this.model = "null"; +} + +void func_ladder_init(entity this) +{ + settouch(this, func_ladder_touch); + trigger_init(this); + func_ladder_link(this); + + if(min(this.absmax.x - this.absmin.x, this.absmax.y - this.absmin.y) > 100) + return; + + entity tracetest_ent = spawn(); + setsize(tracetest_ent, PL_MIN_CONST, PL_MAX_CONST); + tracetest_ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; + + vector top_min = (this.absmin + this.absmax) / 2; + top_min.z = this.absmax.z; + vector top_max = top_min; + top_max.z += PL_MAX_CONST.z - PL_MIN_CONST.z; + tracebox(top_max + jumpstepheightvec, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent); + if(trace_startsolid) + { + tracebox(top_max + stepheightvec, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent); + if(trace_startsolid) + { + tracebox(top_max, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent); + if(trace_startsolid) + { + if(this.absmax.x - this.absmin.x > PL_MAX_CONST.x - PL_MIN_CONST.x + && this.absmax.y - this.absmin.y < this.absmax.x - this.absmin.x) + { + // move top on one side + top_max.y = top_min.y = this.absmin.y + (PL_MAX_CONST.y - PL_MIN_CONST.y) * 0.75; + } + else if(this.absmax.y - this.absmin.y > PL_MAX_CONST.y - PL_MIN_CONST.y + && this.absmax.x - this.absmin.x < this.absmax.y - this.absmin.y) + { + // move top on one side + top_max.x = top_min.x = this.absmin.x + (PL_MAX_CONST.x - PL_MIN_CONST.x) * 0.75; + } + tracebox(top_max, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent); + if(trace_startsolid) + { + if(this.absmax.x - this.absmin.x > PL_MAX_CONST.x - PL_MIN_CONST.x + && this.absmax.y - this.absmin.y < this.absmax.x - this.absmin.x) + { + // alternatively on the other side + top_max.y = top_min.y = this.absmax.y - (PL_MAX_CONST.y - PL_MIN_CONST.y) * 0.75; + } + else if(this.absmax.y - this.absmin.y > PL_MAX_CONST.y - PL_MIN_CONST.y + && this.absmax.x - this.absmin.x < this.absmax.y - this.absmin.y) + { + // alternatively on the other side + top_max.x = top_min.x = this.absmax.x - (PL_MAX_CONST.x - PL_MIN_CONST.x) * 0.75; + } + tracebox(top_max, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent); + } + } + } + } + if(trace_startsolid || trace_endpos.z < this.absmax.z) + { + delete(tracetest_ent); + return; + } + + this.bot_pickup = true; // allow bots to make use of this ladder + float cost = waypoint_getlinearcost(trace_endpos.z - this.absmin.z); + top_min = trace_endpos; + waypoint_spawnforteleporter_boxes(this, WAYPOINTFLAG_LADDER, this.absmin, this.absmax, top_min, top_min, cost); +} + +spawnfunc(func_ladder) +{ + IL_PUSH(g_ladders, this); // TODO: also func_water? bots currently loop through func_ladder only + + func_ladder_init(this); +} + +spawnfunc(func_water) +{ + func_ladder_init(this); +} + +#elif defined(CSQC) +.float speed; + +void func_ladder_remove(entity this) +{ + strfree(this.classname); +} + +NET_HANDLE(ENT_CLIENT_LADDER, bool isnew) +{ + this.classname = strzone(ReadString()); + this.skin = ReadByte(); + this.speed = ReadCoord(); + + trigger_common_read(this, false); + + this.solid = SOLID_TRIGGER; + settouch(this, func_ladder_touch); + this.drawmask = MASK_NORMAL; + this.move_time = time; + this.entremove = func_ladder_remove; + + return true; +} +#endif diff --git a/qcsrc/common/mapobjects/func/ladder.qh b/qcsrc/common/mapobjects/func/ladder.qh new file mode 100644 index 000000000..26cbbda03 --- /dev/null +++ b/qcsrc/common/mapobjects/func/ladder.qh @@ -0,0 +1,4 @@ +#pragma once + +.float ladder_time; +.entity ladder_entity; diff --git a/qcsrc/common/mapobjects/func/pendulum.qc b/qcsrc/common/mapobjects/func/pendulum.qc new file mode 100644 index 000000000..a59f7a93b --- /dev/null +++ b/qcsrc/common/mapobjects/func/pendulum.qc @@ -0,0 +1,77 @@ +#include "pendulum.qh" +#ifdef SVQC +.float freq; +void func_pendulum_controller_think(entity this) +{ + float v; + this.nextthink = time + 0.1; + + if (!(this.owner.active == ACTIVE_ACTIVE)) + { + this.owner.avelocity_x = 0; + return; + } + + // calculate sinewave using makevectors + makevectors((this.nextthink * this.owner.freq + this.owner.phase) * '0 360 0'); + v = this.owner.speed * v_forward_y + this.cnt; + if(this.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed + { + // * 10 so it will arrive in 0.1 sec + this.owner.avelocity_z = (remainder(v - this.owner.angles_z, 360)) * 10; + } +} + +spawnfunc(func_pendulum) +{ + entity controller; + if (this.noise != "") + { + precache_sound(this.noise); + soundto(MSG_INIT, this, CH_TRIGGER_SINGLE, this.noise, VOL_BASE, ATTEN_IDLE); + } + + this.active = ACTIVE_ACTIVE; + + // keys: angle, speed, phase, noise, freq + + if(!this.speed) + this.speed = 30; + // not initializing this.dmg to 2, to allow damageless pendulum + + if(this.dmg && (this.message == "")) + this.message = " was squished"; + if(this.dmg && (this.message2 == "")) + this.message2 = "was squished by"; + if(this.dmg && (!this.dmgtime)) + this.dmgtime = 0.25; + this.dmgtime2 = time; + + setblocked(this, generic_plat_blocked); + + this.avelocity_z = 0.0000001; + if (!InitMovingBrushTrigger(this)) + return; + + if(!this.freq) + { + // find pendulum length (same formula as Q3A) + this.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(this.mins_z)))); + } + + // copy initial angle + this.cnt = this.angles_z; + + // wait for targets to spawn + controller = new(func_pendulum_controller); + controller.owner = this; + controller.nextthink = time + 1; + setthink(controller, func_pendulum_controller_think); + this.nextthink = this.ltime + 999999999; + setthink(this, SUB_NullThink); // for PushMove + + //this.effects |= EF_LOWPRECISION; + + // TODO make a reset function for this one +} +#endif diff --git a/qcsrc/common/mapobjects/func/pendulum.qh b/qcsrc/common/mapobjects/func/pendulum.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/func/pendulum.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/func/plat.qc b/qcsrc/common/mapobjects/func/plat.qc new file mode 100644 index 000000000..b05233621 --- /dev/null +++ b/qcsrc/common/mapobjects/func/plat.qc @@ -0,0 +1,190 @@ +#include "plat.qh" +REGISTER_NET_LINKED(ENT_CLIENT_PLAT) + +#ifdef SVQC +void plat_link(entity this); + +void plat_delayedinit(entity this) +{ + plat_link(this); + plat_spawn_inside_trigger(this); // the "start moving" trigger +} + +float plat_send(entity this, entity to, float sf) +{ + WriteHeader(MSG_ENTITY, ENT_CLIENT_PLAT); + WriteByte(MSG_ENTITY, sf); + + if(sf & SF_TRIGGER_INIT) + { + WriteByte(MSG_ENTITY, this.platmovetype_start); + WriteByte(MSG_ENTITY, this.platmovetype_turn); + WriteByte(MSG_ENTITY, this.platmovetype_end); + WriteByte(MSG_ENTITY, this.spawnflags); + + WriteString(MSG_ENTITY, this.model); + + trigger_common_write(this, true); + + WriteVector(MSG_ENTITY, this.pos1); + WriteVector(MSG_ENTITY, this.pos2); + + WriteVector(MSG_ENTITY, this.size); + + WriteAngle(MSG_ENTITY, this.mangle_x); + WriteAngle(MSG_ENTITY, this.mangle_y); + WriteAngle(MSG_ENTITY, this.mangle_z); + + WriteShort(MSG_ENTITY, this.speed); + WriteShort(MSG_ENTITY, this.height); + WriteByte(MSG_ENTITY, this.lip); + WriteByte(MSG_ENTITY, this.state); + + WriteShort(MSG_ENTITY, this.dmg); + } + + if(sf & SF_TRIGGER_RESET) + { + // used on client + } + + return true; +} + +void plat_link(entity this) +{ + //Net_LinkEntity(this, 0, false, plat_send); +} + +spawnfunc(func_plat) +{ + if (this.spawnflags & CRUSH) + { + this.dmg = 10000; + } + + if (this.dmg && (this.message == "")) + { + this.message = "was squished"; + } + if (this.dmg && (this.message2 == "")) + { + this.message2 = "was squished by"; + } + + if (this.sounds == 1) + { + this.noise = "plats/plat1.wav"; + this.noise1 = "plats/plat2.wav"; + } + + if (this.sounds == 2) + { + this.noise = "plats/medplat1.wav"; + this.noise1 = "plats/medplat2.wav"; + } + + // WARNING: backwards compatibility because people don't use already existing fields :( + if (this.sound1) + this.noise = this.sound1; + if (this.sound2) + this.noise1 = this.sound2; + + if(this.noise && this.noise != "") + { + precache_sound(this.noise); + } + if(this.noise1 && this.noise1 != "") + { + precache_sound(this.noise1); + } + + this.mangle = this.angles; + this.angles = '0 0 0'; + + this.classname = "plat"; + if (!InitMovingBrushTrigger(this)) + return; + this.effects |= EF_LOWPRECISION; + setsize (this, this.mins , this.maxs); + + setblocked(this, plat_crush); + + if (!this.speed) this.speed = 150; + if (!this.lip) this.lip = 16; + if (!this.height) this.height = this.size.z - this.lip; + + this.pos1 = this.origin; + this.pos2 = this.origin; + this.pos2_z = this.origin.z - this.height; + + this.reset = plat_reset; + this.reset(this); + + InitializeEntity(this, plat_delayedinit, INITPRIO_FINDTARGET); +} +#elif defined(CSQC) +void plat_draw(entity this) +{ + Movetype_Physics_NoMatchServer(this); + //Movetype_Physics_MatchServer(autocvar_cl_projectiles_sloppy); +} + +NET_HANDLE(ENT_CLIENT_PLAT, bool isnew) +{ + float sf = ReadByte(); + + if(sf & SF_TRIGGER_INIT) + { + this.platmovetype_start = ReadByte(); + this.platmovetype_turn = ReadByte(); + this.platmovetype_end = ReadByte(); + this.spawnflags = ReadByte(); + + this.model = strzone(ReadString()); + _setmodel(this, this.model); + + trigger_common_read(this, true); + + this.pos1 = ReadVector(); + this.pos2 = ReadVector(); + + this.size = ReadVector(); + + this.mangle_x = ReadAngle(); + this.mangle_y = ReadAngle(); + this.mangle_z = ReadAngle(); + + this.speed = ReadShort(); + this.height = ReadShort(); + this.lip = ReadByte(); + this.state = ReadByte(); + + this.dmg = ReadShort(); + + this.classname = "plat"; + this.solid = SOLID_BSP; + set_movetype(this, MOVETYPE_PUSH); + this.drawmask = MASK_NORMAL; + this.draw = plat_draw; + if (isnew) IL_PUSH(g_drawables, this); + this.use = plat_use; + this.entremove = trigger_remove_generic; + + plat_reset(this); // also called here + + set_movetype(this, MOVETYPE_PUSH); + this.move_time = time; + + plat_spawn_inside_trigger(this); + } + + if(sf & SF_TRIGGER_RESET) + { + plat_reset(this); + + this.move_time = time; + } + return true; +} +#endif diff --git a/qcsrc/common/mapobjects/func/plat.qh b/qcsrc/common/mapobjects/func/plat.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/func/plat.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/func/pointparticles.qc b/qcsrc/common/mapobjects/func/pointparticles.qc new file mode 100644 index 000000000..7de5a03ef --- /dev/null +++ b/qcsrc/common/mapobjects/func/pointparticles.qc @@ -0,0 +1,341 @@ +#include "pointparticles.qh" +REGISTER_NET_LINKED(ENT_CLIENT_POINTPARTICLES) + +#ifdef SVQC +// NOTE: also contains func_sparks + +bool pointparticles_SendEntity(entity this, entity to, float sendflags) +{ + WriteHeader(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES); + + // optional features to save space + sendflags = sendflags & 0x0F; + if(this.spawnflags & PARTICLES_IMPULSE) + sendflags |= SF_POINTPARTICLES_IMPULSE; // absolute count on toggle-on + if(this.movedir != '0 0 0' || this.velocity != '0 0 0') + sendflags |= SF_POINTPARTICLES_MOVING; // 4 bytes - saves CPU + if(this.waterlevel || this.count != 1) + sendflags |= SF_POINTPARTICLES_JITTER_AND_COUNT; // 4 bytes - obscure features almost never used + if(this.mins != '0 0 0' || this.maxs != '0 0 0') + sendflags |= SF_POINTPARTICLES_BOUNDS; // 14 bytes - saves lots of space + + WriteByte(MSG_ENTITY, sendflags); + if(sendflags & SF_TRIGGER_UPDATE) + { + if(this.active == ACTIVE_ACTIVE) + WriteCoord(MSG_ENTITY, this.impulse); + else + WriteCoord(MSG_ENTITY, 0); // off + } + if(sendflags & SF_TRIGGER_RESET) + { + WriteVector(MSG_ENTITY, this.origin); + } + if(sendflags & SF_TRIGGER_INIT) + { + if(this.model != "null") + { + WriteShort(MSG_ENTITY, this.modelindex); + if(sendflags & SF_POINTPARTICLES_BOUNDS) + { + WriteVector(MSG_ENTITY, this.mins); + WriteVector(MSG_ENTITY, this.maxs); + } + } + else + { + WriteShort(MSG_ENTITY, 0); + if(sendflags & SF_POINTPARTICLES_BOUNDS) + { + WriteVector(MSG_ENTITY, this.maxs); + } + } + WriteShort(MSG_ENTITY, this.cnt); + WriteString(MSG_ENTITY, this.mdl); + if(sendflags & SF_POINTPARTICLES_MOVING) + { + WriteShort(MSG_ENTITY, compressShortVector(this.velocity)); + WriteShort(MSG_ENTITY, compressShortVector(this.movedir)); + } + if(sendflags & SF_POINTPARTICLES_JITTER_AND_COUNT) + { + WriteShort(MSG_ENTITY, this.waterlevel * 16.0); + WriteByte(MSG_ENTITY, this.count * 16.0); + } + WriteString(MSG_ENTITY, this.noise); + if(this.noise != "") + { + WriteByte(MSG_ENTITY, floor(this.atten * 64)); + WriteByte(MSG_ENTITY, floor(this.volume * 255)); + } + WriteString(MSG_ENTITY, this.bgmscript); + if(this.bgmscript != "") + { + WriteByte(MSG_ENTITY, floor(this.bgmscriptattack * 64)); + WriteByte(MSG_ENTITY, floor(this.bgmscriptdecay * 64)); + WriteByte(MSG_ENTITY, floor(this.bgmscriptsustain * 255)); + WriteByte(MSG_ENTITY, floor(this.bgmscriptrelease * 64)); + } + } + return 1; +} + +void pointparticles_think(entity this) +{ + if(this.origin != this.oldorigin) + { + this.SendFlags |= SF_TRIGGER_RESET; + this.oldorigin = this.origin; + } + this.nextthink = time; +} + +spawnfunc(func_pointparticles) +{ + if(this.model != "") { precache_model(this.model); _setmodel(this, this.model); } + if(this.noise != "") precache_sound(this.noise); + if(this.mdl != "") this.cnt = 0; // use a good handler + + if(!this.bgmscriptsustain) this.bgmscriptsustain = 1; + else if(this.bgmscriptsustain < 0) this.bgmscriptsustain = 0; + + if(!this.atten) this.atten = ATTEN_NORM; + else if(this.atten < 0) this.atten = 0; + if(!this.volume) this.volume = 1; + if(!this.count) this.count = 1; + if(!this.impulse) this.impulse = 1; + + if(!this.modelindex) + { + setorigin(this, this.origin + this.mins); + setsize(this, '0 0 0', this.maxs - this.mins); + } + //if(!this.cnt) this.cnt = _particleeffectnum(this.mdl); + this.setactive = generic_netlinked_setactive; + + Net_LinkEntity(this, (this.spawnflags & PARTICLES_VISCULLING), 0, pointparticles_SendEntity); + + IFTARGETED + { + // backwards compatibility + this.use = generic_netlinked_legacy_use; + } + this.reset = generic_netlinked_reset; + this.reset(this); + setthink(this, pointparticles_think); + this.nextthink = time; +} + +spawnfunc(func_sparks) +{ + if(this.count < 1) { + this.count = 25.0; // nice default value + } + + if(this.impulse < 0.5) { + this.impulse = 2.5; // nice default value + } + + this.mins = '0 0 0'; + this.maxs = '0 0 0'; + this.velocity = '0 0 -1'; + this.mdl = "TE_SPARK"; + this.cnt = 0; // use mdl + + spawnfunc_func_pointparticles(this); +} +#elif defined(CSQC) + +.int dphitcontentsmask; + +entityclass(PointParticles); +classfield(PointParticles) .int cnt; // effect number +classfield(PointParticles) .vector velocity; // particle velocity +classfield(PointParticles) .float waterlevel; // direction jitter +classfield(PointParticles) .int count; // count multiplier +classfield(PointParticles) .int impulse; // density +classfield(PointParticles) .string noise; // sound +classfield(PointParticles) .float atten; +classfield(PointParticles) .float volume; +classfield(PointParticles) .float absolute; // 1 = count per second is absolute, ABSOLUTE_ONLY_SPAWN_AT_TOGGLE = only spawn at toggle +classfield(PointParticles) .vector movedir; // trace direction +classfield(PointParticles) .float glow_color; // palette index + +const int ABSOLUTE_ONLY_SPAWN_AT_TOGGLE = 2; + +void Draw_PointParticles(entity this) +{ + float n, i, fail; + vector p; + vector sz; + vector o; + o = this.origin; + sz = this.maxs - this.mins; + n = doBGMScript(this); + if(this.absolute == ABSOLUTE_ONLY_SPAWN_AT_TOGGLE) + { + if(n >= 0) + n = this.just_toggled ? this.impulse : 0; + else + n = this.impulse * drawframetime; + } + else + { + n *= this.impulse * drawframetime; + if(this.just_toggled) + if(n < 1) + n = 1; + } + if(n == 0) + return; + fail = 0; + for(i = random(); i <= n && fail <= 64*n; ++i) + { + p = o + this.mins; + p.x += random() * sz.x; + p.y += random() * sz.y; + p.z += random() * sz.z; + if(WarpZoneLib_BoxTouchesBrush(p, p, this, NULL)) + { + if(this.movedir != '0 0 0') + { + traceline(p, p + normalize(this.movedir) * 4096, 0, NULL); + p = trace_endpos; + int eff_num; + if(this.cnt) + eff_num = this.cnt; + else + eff_num = _particleeffectnum(this.mdl); + __pointparticles(eff_num, p, trace_plane_normal * vlen(this.movedir) + this.velocity + randomvec() * this.waterlevel, this.count); + } + else + { + int eff_num; + if(this.cnt) + eff_num = this.cnt; + else + eff_num = _particleeffectnum(this.mdl); + __pointparticles(eff_num, p, this.velocity + randomvec() * this.waterlevel, this.count); + } + if(this.noise != "") + { + setorigin(this, p); + _sound(this, CH_AMBIENT, this.noise, VOL_BASE * this.volume, this.atten); + } + this.just_toggled = 0; + } + else if(this.absolute) + { + ++fail; + --i; + } + } + setorigin(this, o); +} + +void Ent_PointParticles_Remove(entity this) +{ + strfree(this.noise); + strfree(this.bgmscript); + strfree(this.mdl); +} + +NET_HANDLE(ENT_CLIENT_POINTPARTICLES, bool isnew) +{ + float i; + vector v; + int sendflags = ReadByte(); + if(sendflags & SF_TRIGGER_UPDATE) + { + i = ReadCoord(); // density (<0: point, >0: volume) + if(i && !this.impulse && (this.cnt || this.mdl)) // this.cnt check is so it only happens if the ent already existed + this.just_toggled = 1; + this.impulse = i; + } + if(sendflags & SF_TRIGGER_RESET) + { + this.origin = ReadVector(); + } + if(sendflags & SF_TRIGGER_INIT) + { + this.modelindex = ReadShort(); + if(sendflags & SF_POINTPARTICLES_BOUNDS) + { + if(this.modelindex) + { + this.mins = ReadVector(); + this.maxs = ReadVector(); + } + else + { + this.mins = '0 0 0'; + this.maxs = ReadVector(); + } + } + else + { + this.mins = this.maxs = '0 0 0'; + } + + this.cnt = ReadShort(); // effect number + this.mdl = strzone(ReadString()); // effect string + + if(sendflags & SF_POINTPARTICLES_MOVING) + { + this.velocity = decompressShortVector(ReadShort()); + this.movedir = decompressShortVector(ReadShort()); + } + else + { + this.velocity = this.movedir = '0 0 0'; + } + if(sendflags & SF_POINTPARTICLES_JITTER_AND_COUNT) + { + this.waterlevel = ReadShort() / 16.0; + this.count = ReadByte() / 16.0; + } + else + { + this.waterlevel = 0; + this.count = 1; + } + strcpy(this.noise, ReadString()); + if(this.noise != "") + { + this.atten = ReadByte() / 64.0; + this.volume = ReadByte() / 255.0; + } + strcpy(this.bgmscript, ReadString()); + if(this.bgmscript != "") + { + this.bgmscriptattack = ReadByte() / 64.0; + this.bgmscriptdecay = ReadByte() / 64.0; + this.bgmscriptsustain = ReadByte() / 255.0; + this.bgmscriptrelease = ReadByte() / 64.0; + } + BGMScript_InitEntity(this); + } + + return = true; + + if(sendflags & SF_TRIGGER_UPDATE) + { + this.absolute = (this.impulse >= 0); + if(!this.absolute) + { + v = this.maxs - this.mins; + this.impulse *= -v.x * v.y * v.z / (64**3); // relative: particles per 64^3 cube + } + } + + if(sendflags & SF_POINTPARTICLES_IMPULSE) + this.absolute = ABSOLUTE_ONLY_SPAWN_AT_TOGGLE; + + setorigin(this, this.origin); + setsize(this, this.mins, this.maxs); + this.solid = SOLID_NOT; + this.draw = Draw_PointParticles; + if (isnew) IL_PUSH(g_drawables, this); + this.entremove = Ent_PointParticles_Remove; +} +#endif diff --git a/qcsrc/common/mapobjects/func/pointparticles.qh b/qcsrc/common/mapobjects/func/pointparticles.qh new file mode 100644 index 000000000..b167527bc --- /dev/null +++ b/qcsrc/common/mapobjects/func/pointparticles.qh @@ -0,0 +1,11 @@ +#pragma once + +// spawnflags +const int PARTICLES_IMPULSE = BIT(1); +const int PARTICLES_VISCULLING = BIT(2); + +// sendflags +const int SF_POINTPARTICLES_IMPULSE = BIT(4); +const int SF_POINTPARTICLES_MOVING = BIT(5); // Send velocity and movedir +const int SF_POINTPARTICLES_JITTER_AND_COUNT = BIT(6); // Send waterlevel (=jitter) and count +const int SF_POINTPARTICLES_BOUNDS = BIT(7); // Send min and max of the brush diff --git a/qcsrc/common/mapobjects/func/rainsnow.qc b/qcsrc/common/mapobjects/func/rainsnow.qc new file mode 100644 index 000000000..c765a4293 --- /dev/null +++ b/qcsrc/common/mapobjects/func/rainsnow.qc @@ -0,0 +1,142 @@ +#include "rainsnow.qh" +REGISTER_NET_LINKED(ENT_CLIENT_RAINSNOW) + +#ifdef SVQC +bool rainsnow_SendEntity(entity this, entity to, float sf) +{ + vector myorg = this.origin + this.mins; + vector mysize = this.maxs - this.mins; + WriteHeader(MSG_ENTITY, ENT_CLIENT_RAINSNOW); + WriteByte(MSG_ENTITY, this.state); + WriteVector(MSG_ENTITY, myorg); + WriteVector(MSG_ENTITY, mysize); + WriteShort(MSG_ENTITY, compressShortVector(this.dest)); + WriteShort(MSG_ENTITY, this.count); + WriteByte(MSG_ENTITY, this.cnt); + return true; +} + +/*QUAKED spawnfunc_func_rain (0 .5 .8) ? +This is an invisible area like a trigger, which rain falls inside of. + +Keys: +"velocity" + falling direction (should be something like '0 0 -700', use the X and Y velocity for wind) +"cnt" + sets color of rain (default 12 - white) +"count" + adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000 +*/ +spawnfunc(func_rain) +{ + this.dest = this.velocity; + this.velocity = '0 0 0'; + if (!this.dest) + this.dest = '0 0 -700'; + this.angles = '0 0 0'; + set_movetype(this, MOVETYPE_NONE); + this.solid = SOLID_NOT; + SetBrushEntityModel(this); + if (!this.cnt) + { + this.cnt = 12; + } + if (!this.count) + this.count = 2000; + // relative to absolute particle count + this.count = 0.1 * this.count * (this.size_x / 1024) * (this.size_y / 1024); + if (this.count < 1) + this.count = 1; + if(this.count > 65535) + this.count = 65535; + + this.state = RAINSNOW_RAIN; + + Net_LinkEntity(this, false, 0, rainsnow_SendEntity); +} + + +/*QUAKED spawnfunc_func_snow (0 .5 .8) ? +This is an invisible area like a trigger, which snow falls inside of. + +Keys: +"velocity" + falling direction (should be something like '0 0 -300', use the X and Y velocity for wind) +"cnt" + sets color of rain (default 12 - white) +"count" + adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000 +*/ +spawnfunc(func_snow) +{ + this.dest = this.velocity; + this.velocity = '0 0 0'; + if (!this.dest) + this.dest = '0 0 -300'; + this.angles = '0 0 0'; + set_movetype(this, MOVETYPE_NONE); + this.solid = SOLID_NOT; + SetBrushEntityModel(this); + if (!this.cnt) + { + this.cnt = 12; + } + if (!this.count) + this.count = 2000; + // relative to absolute particle count + this.count = 0.1 * this.count * (this.size_x / 1024) * (this.size_y / 1024); + if (this.count < 1) + this.count = 1; + if(this.count > 65535) + this.count = 65535; + + this.state = RAINSNOW_SNOW; + + Net_LinkEntity(this, false, 0, rainsnow_SendEntity); +} +#elif defined(CSQC) +float autocvar_cl_rainsnow_maxdrawdist = 2048; + +void Draw_Rain(entity this) +{ + vector maxdist = '1 1 0' * autocvar_cl_rainsnow_maxdrawdist; + maxdist.z = 5; + if(boxesoverlap(vec2(view_origin) - maxdist, vec2(view_origin) + maxdist, vec2(this.absmin) - '0 0 5', vec2(this.absmax) + '0 0 5')) + //if(autocvar_cl_rainsnow_maxdrawdist <= 0 || vdist(vec2(this.origin) - vec2(this.absmin + this.absmax * 0.5), <=, autocvar_cl_rainsnow_maxdrawdist)) + te_particlerain(this.origin + this.mins, this.origin + this.maxs, this.velocity, floor(this.count * drawframetime + random()), this.glow_color); +} + +void Draw_Snow(entity this) +{ + vector maxdist = '1 1 0' * autocvar_cl_rainsnow_maxdrawdist; + maxdist.z = 5; + if(boxesoverlap(vec2(view_origin) - maxdist, vec2(view_origin) + maxdist, vec2(this.absmin) - '0 0 5', vec2(this.absmax) + '0 0 5')) + //if(autocvar_cl_rainsnow_maxdrawdist <= 0 || vdist(vec2(this.origin) - vec2(this.absmin + this.absmax * 0.5), <=, autocvar_cl_rainsnow_maxdrawdist)) + te_particlesnow(this.origin + this.mins, this.origin + this.maxs, this.velocity, floor(this.count * drawframetime + random()), this.glow_color); +} + +NET_HANDLE(ENT_CLIENT_RAINSNOW, bool isnew) +{ + this.state = ReadByte(); // Rain, Snow, or Whatever + this.origin = ReadVector(); + this.maxs = ReadVector(); + this.velocity = decompressShortVector(ReadShort()); + this.count = ReadShort(); + this.glow_color = ReadByte(); // color + + return = true; + + this.mins = -0.5 * this.maxs; + this.maxs = 0.5 * this.maxs; + this.origin = this.origin - this.mins; + + setorigin(this, this.origin); + setsize(this, this.mins, this.maxs); + this.solid = SOLID_NOT; + if (isnew) IL_PUSH(g_drawables, this); + if(this.state == RAINSNOW_RAIN) + this.draw = Draw_Rain; + else + this.draw = Draw_Snow; +} +#endif diff --git a/qcsrc/common/mapobjects/func/rainsnow.qh b/qcsrc/common/mapobjects/func/rainsnow.qh new file mode 100644 index 000000000..d60eb4f48 --- /dev/null +++ b/qcsrc/common/mapobjects/func/rainsnow.qh @@ -0,0 +1,5 @@ +#pragma once + + +const int RAINSNOW_SNOW = 0; +const int RAINSNOW_RAIN = 1; diff --git a/qcsrc/common/mapobjects/func/rotating.qc b/qcsrc/common/mapobjects/func/rotating.qc new file mode 100644 index 000000000..35351ee08 --- /dev/null +++ b/qcsrc/common/mapobjects/func/rotating.qc @@ -0,0 +1,110 @@ +#include "rotating.qh" +#ifdef SVQC + +void func_rotating_setactive(entity this, int astate) +{ + if (astate == ACTIVE_TOGGLE) + { + if(this.active == ACTIVE_ACTIVE) + this.active = ACTIVE_NOT; + else + this.active = ACTIVE_ACTIVE; + } + else + this.active = astate; + + if(this.active == ACTIVE_NOT) + { + this.avelocity = '0 0 0'; + stopsound(this, CH_AMBIENT_SINGLE); + } + else + { + this.avelocity = this.pos1; + if(this.noise && this.noise != "") + { + _sound(this, CH_AMBIENT_SINGLE, this.noise, VOL_BASE, ATTEN_IDLE); + } + } +} + +void func_rotating_reset(entity this) +{ + // TODO: reset angles as well? + + if(this.spawnflags & FUNC_ROTATING_STARTOFF) + { + this.setactive(this, ACTIVE_NOT); + } + else + { + this.setactive(this, ACTIVE_ACTIVE); + } +} + +void func_rotating_init_for_player(entity this, entity player) +{ + if (this.noise && this.noise != "" && this.active == ACTIVE_ACTIVE && IS_REAL_CLIENT(player)) + { + msg_entity = player; + soundto (MSG_ONE, this, CH_AMBIENT_SINGLE, this.noise, VOL_BASE, ATTEN_IDLE); + } +} + +/*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS +Brush model that spins in place on one axis (default Z). +speed : speed to rotate (in degrees per second) +noise : path/name of looping .wav file to play. +dmg : Do this mutch dmg every .dmgtime intervall when blocked +dmgtime : See above. +*/ + +spawnfunc(func_rotating) +{ + if (this.noise && this.noise != "") + { + precache_sound(this.noise); + } + + this.setactive = func_rotating_setactive; + + if (!this.speed) + this.speed = 100; + if (this.spawnflags & FUNC_ROTATING_XAXIS) + this.avelocity = '0 0 1' * this.speed; + else if (this.spawnflags & FUNC_ROTATING_YAXIS) + this.avelocity = '1 0 0' * this.speed; + else // Z + this.avelocity = '0 1 0' * this.speed; + + this.pos1 = this.avelocity; + + if(this.dmg && (this.message == "")) + this.message = " was squished"; + if(this.dmg && (this.message2 == "")) + this.message2 = "was squished by"; + + + if(this.dmg && (!this.dmgtime)) + this.dmgtime = 0.25; + + this.dmgtime2 = time; + + if (!InitMovingBrushTrigger(this)) + return; + // no EF_LOWPRECISION here, as rounding angles is bad + + setblocked(this, generic_plat_blocked); + + // wait for targets to spawn + this.nextthink = this.ltime + 999999999; + setthink(this, SUB_NullThink); // for PushMove + + this.reset = func_rotating_reset; + this.reset(this); + + // maybe send sound to new players + IL_PUSH(g_initforplayer, this); + this.init_for_player = func_rotating_init_for_player; +} +#endif diff --git a/qcsrc/common/mapobjects/func/rotating.qh b/qcsrc/common/mapobjects/func/rotating.qh new file mode 100644 index 000000000..ad1b6ec92 --- /dev/null +++ b/qcsrc/common/mapobjects/func/rotating.qh @@ -0,0 +1,6 @@ +#pragma once + + +const int FUNC_ROTATING_XAXIS = BIT(2); +const int FUNC_ROTATING_YAXIS = BIT(3); +const int FUNC_ROTATING_STARTOFF = BIT(4); diff --git a/qcsrc/common/mapobjects/func/stardust.qc b/qcsrc/common/mapobjects/func/stardust.qc new file mode 100644 index 000000000..9c2fba8ad --- /dev/null +++ b/qcsrc/common/mapobjects/func/stardust.qc @@ -0,0 +1,9 @@ +#include "stardust.qh" +#ifdef SVQC +spawnfunc(func_stardust) +{ + this.effects = EF_STARDUST; + + CSQCMODEL_AUTOINIT(this); +} +#endif diff --git a/qcsrc/common/mapobjects/func/stardust.qh b/qcsrc/common/mapobjects/func/stardust.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/func/stardust.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/func/train.qc b/qcsrc/common/mapobjects/func/train.qc new file mode 100644 index 000000000..4e9c33456 --- /dev/null +++ b/qcsrc/common/mapobjects/func/train.qc @@ -0,0 +1,351 @@ +#include "train.qh" +.float train_wait_turning; +.entity future_target; +void train_next(entity this); +#ifdef SVQC +void train_use(entity this, entity actor, entity trigger); +#endif +void train_wait(entity this) +{ + SUB_UseTargets(this.enemy, NULL, NULL); + this.enemy = NULL; + + // if turning is enabled, the train will turn toward the next point while waiting + if(this.platmovetype_turn && !this.train_wait_turning) + { + entity targ, cp; + vector ang; + targ = this.future_target; + if((this.spawnflags & TRAIN_CURVE) && targ.curvetarget) + cp = find(NULL, targetname, targ.curvetarget); + else + cp = NULL; + + if(cp) // bezier curves movement + ang = cp.origin - (this.origin - this.view_ofs); // use the origin of the control point of the next path_corner + else // linear movement + ang = targ.origin - (this.origin - this.view_ofs); // use the origin of the next path_corner + ang = vectoangles(ang); + ang_x = -ang_x; // flip up / down orientation + + if(this.wait > 0) // slow turning + SUB_CalcAngleMove(this, ang, TSPEED_TIME, this.ltime - time + this.wait, train_wait); + else // instant turning + SUB_CalcAngleMove(this, ang, TSPEED_TIME, 0.0000001, train_wait); + this.train_wait_turning = true; + return; + } + +#ifdef SVQC + if(this.noise != "") + stopsoundto(MSG_BROADCAST, this, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway +#endif + +#ifdef SVQC + entity tg = this.future_target; + if(tg.spawnflags & TRAIN_NEEDACTIVATION) + { + this.use = train_use; + setthink(this, func_null); + this.nextthink = 0; + } + else +#endif + if(this.wait < 0 || this.train_wait_turning) // no waiting or we already waited while turning + { + this.train_wait_turning = false; + train_next(this); + } + else + { + setthink(this, train_next); + this.nextthink = this.ltime + this.wait; + } +} + +entity train_next_find(entity this) +{ + if(this.target_random) + { + RandomSelection_Init(); + for(entity t = NULL; (t = find(t, targetname, this.target));) + { + RandomSelection_AddEnt(t, 1, 0); + } + return RandomSelection_chosen_ent; + } + else + { + return find(NULL, targetname, this.target); + } +} + +void train_next(entity this) +{ + entity targ = NULL, cp = NULL; + vector cp_org = '0 0 0'; + + targ = this.future_target; + + this.target = targ.target; + this.target_random = targ.target_random; + this.future_target = train_next_find(targ); + + if (this.spawnflags & TRAIN_CURVE) + { + if(targ.curvetarget) + { + cp = find(NULL, targetname, targ.curvetarget); // get its second target (the control point) + cp_org = cp.origin - this.view_ofs; // no control point found, assume a straight line to the destination + } + } + if (this.target == "") + objerror(this, "train_next: no next target"); + this.wait = targ.wait; + if (!this.wait) + this.wait = 0.1; + + if(targ.platmovetype) + { + // this path_corner contains a movetype overrider, apply it + this.platmovetype_start = targ.platmovetype_start; + this.platmovetype_end = targ.platmovetype_end; + } + else + { + // this path_corner doesn't contain a movetype overrider, use the train's defaults + this.platmovetype_start = this.platmovetype_start_default; + this.platmovetype_end = this.platmovetype_end_default; + } + + if (targ.speed) + { + if (cp) + SUB_CalcMove_Bezier(this, cp_org, targ.origin - this.view_ofs, TSPEED_LINEAR, targ.speed, train_wait); + else + SUB_CalcMove(this, targ.origin - this.view_ofs, TSPEED_LINEAR, targ.speed, train_wait); + } + else + { + if (cp) + SUB_CalcMove_Bezier(this, cp_org, targ.origin - this.view_ofs, TSPEED_LINEAR, this.speed, train_wait); + else + SUB_CalcMove(this, targ.origin - this.view_ofs, TSPEED_LINEAR, this.speed, train_wait); + } + + if(this.noise != "") + _sound(this, CH_TRIGGER_SINGLE, this.noise, VOL_BASE, ATTEN_IDLE); +} + +REGISTER_NET_LINKED(ENT_CLIENT_TRAIN) + +#ifdef SVQC +float train_send(entity this, entity to, float sf) +{ + WriteHeader(MSG_ENTITY, ENT_CLIENT_TRAIN); + WriteByte(MSG_ENTITY, sf); + + if(sf & SF_TRIGGER_INIT) + { + WriteString(MSG_ENTITY, this.platmovetype); + WriteByte(MSG_ENTITY, this.platmovetype_turn); + WriteByte(MSG_ENTITY, this.spawnflags); + + WriteString(MSG_ENTITY, this.model); + + trigger_common_write(this, true); + + WriteString(MSG_ENTITY, this.curvetarget); + + WriteVector(MSG_ENTITY, this.pos1); + WriteVector(MSG_ENTITY, this.pos2); + + WriteVector(MSG_ENTITY, this.size); + + WriteVector(MSG_ENTITY, this.view_ofs); + + WriteAngle(MSG_ENTITY, this.mangle_x); + WriteAngle(MSG_ENTITY, this.mangle_y); + WriteAngle(MSG_ENTITY, this.mangle_z); + + WriteShort(MSG_ENTITY, this.speed); + WriteShort(MSG_ENTITY, this.height); + WriteByte(MSG_ENTITY, this.lip); + WriteByte(MSG_ENTITY, this.state); + WriteByte(MSG_ENTITY, this.wait); + + WriteShort(MSG_ENTITY, this.dmg); + WriteByte(MSG_ENTITY, this.dmgtime); + } + + if(sf & SF_TRIGGER_RESET) + { + // used on client + } + + return true; +} + +void train_link(entity this) +{ + //Net_LinkEntity(this, 0, false, train_send); +} + +void train_use(entity this, entity actor, entity trigger) +{ + this.nextthink = this.ltime + 1; + setthink(this, train_next); + this.use = func_null; // not again, next target can set it again if needed + if(trigger.target2 && trigger.target2 != "") + this.future_target = find(NULL, targetname, trigger.target2); +} + +void func_train_find(entity this) +{ + entity targ = train_next_find(this); + this.target = targ.target; + this.target_random = targ.target_random; + // save the future target for later + this.future_target = train_next_find(targ); + if (this.target == "") + objerror(this, "func_train_find: no next target"); + setorigin(this, targ.origin - this.view_ofs); + + if(!(this.spawnflags & TRAIN_NEEDACTIVATION)) + { + this.nextthink = this.ltime + 1; + setthink(this, train_next); + } + + train_link(this); +} + +#endif + +/*QUAKED spawnfunc_func_train (0 .5 .8) ? +Ridable platform, targets spawnfunc_path_corner path to follow. +speed : speed the train moves (can be overridden by each spawnfunc_path_corner) +target : targetname of first spawnfunc_path_corner (starts here) +*/ +#ifdef SVQC +spawnfunc(func_train) +{ + if (this.noise != "") + precache_sound(this.noise); + + if (this.target == "") + objerror(this, "func_train without a target"); + if (!this.speed) + this.speed = 100; + + if (!InitMovingBrushTrigger(this)) + return; + this.effects |= EF_LOWPRECISION; + + if(this.spawnflags & TRAIN_NEEDACTIVATION) + this.use = train_use; + + if (this.spawnflags & TRAIN_TURN) + { + this.platmovetype_turn = true; + this.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now + } + else + this.view_ofs = this.mins; + + // wait for targets to spawn + InitializeEntity(this, func_train_find, INITPRIO_FINDTARGET); + + setblocked(this, generic_plat_blocked); + if(this.dmg && (this.message == "")) + this.message = " was squished"; + if(this.dmg && (this.message2 == "")) + this.message2 = "was squished by"; + if(this.dmg && (!this.dmgtime)) + this.dmgtime = 0.25; + this.dmgtime2 = time; + + if(!set_platmovetype(this, this.platmovetype)) + return; + this.platmovetype_start_default = this.platmovetype_start; + this.platmovetype_end_default = this.platmovetype_end; + + // TODO make a reset function for this one +} +#elif defined(CSQC) +void train_draw(entity this) +{ + //Movetype_Physics_NoMatchServer(); + Movetype_Physics_MatchServer(this, autocvar_cl_projectiles_sloppy); +} + +NET_HANDLE(ENT_CLIENT_TRAIN, bool isnew) +{ + float sf = ReadByte(); + + if(sf & SF_TRIGGER_INIT) + { + this.platmovetype = strzone(ReadString()); + this.platmovetype_turn = ReadByte(); + this.spawnflags = ReadByte(); + + this.model = strzone(ReadString()); + _setmodel(this, this.model); + + trigger_common_read(this, true); + + this.curvetarget = strzone(ReadString()); + + this.pos1 = ReadVector(); + this.pos2 = ReadVector(); + + this.size = ReadVector(); + + this.view_ofs = ReadVector(); + + this.mangle_x = ReadAngle(); + this.mangle_y = ReadAngle(); + this.mangle_z = ReadAngle(); + + this.speed = ReadShort(); + this.height = ReadShort(); + this.lip = ReadByte(); + this.state = ReadByte(); + this.wait = ReadByte(); + + this.dmg = ReadShort(); + this.dmgtime = ReadByte(); + + this.classname = "func_train"; + this.solid = SOLID_BSP; + set_movetype(this, MOVETYPE_PUSH); + this.drawmask = MASK_NORMAL; + this.draw = train_draw; + if (isnew) IL_PUSH(g_drawables, this); + this.entremove = trigger_remove_generic; + + if(set_platmovetype(this, this.platmovetype)) + { + this.platmovetype_start_default = this.platmovetype_start; + this.platmovetype_end_default = this.platmovetype_end; + } + + // everything is set up by the time the train is linked, we shouldn't need this + //func_train_find(); + + // but we will need these + train_next(this); + + set_movetype(this, MOVETYPE_PUSH); + this.move_time = time; + } + + if(sf & SF_TRIGGER_RESET) + { + // TODO: make a reset function for trains + } + + return true; +} + +#endif diff --git a/qcsrc/common/mapobjects/func/train.qh b/qcsrc/common/mapobjects/func/train.qh new file mode 100644 index 000000000..0b2a099c5 --- /dev/null +++ b/qcsrc/common/mapobjects/func/train.qh @@ -0,0 +1,10 @@ +#pragma once + + +const int TRAIN_CURVE = BIT(0); +const int TRAIN_TURN = BIT(1); +const int TRAIN_NEEDACTIVATION = BIT(2); + +#ifdef CSQC +.float dmgtime; +#endif diff --git a/qcsrc/common/mapobjects/func/vectormamamam.qc b/qcsrc/common/mapobjects/func/vectormamamam.qc new file mode 100644 index 000000000..61da52acb --- /dev/null +++ b/qcsrc/common/mapobjects/func/vectormamamam.qc @@ -0,0 +1,194 @@ +#include "vectormamamam.qh" +#ifdef SVQC +// reusing some fields havocbots declared +.entity wp00, wp01, wp02, wp03; + +.float targetfactor, target2factor, target3factor, target4factor; +.vector targetnormal, target2normal, target3normal, target4normal; + +vector func_vectormamamam_origin(entity o, float timestep) +{ + vector v, p; + float flags; + entity e; + + flags = o.spawnflags; + v = '0 0 0'; + + e = o.wp00; + if(e) + { + p = e.origin + timestep * e.velocity; + if(flags & PROJECT_ON_TARGETNORMAL) + v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor; + else + v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor; + } + + e = o.wp01; + if(e) + { + p = e.origin + timestep * e.velocity; + if(flags & PROJECT_ON_TARGET2NORMAL) + v = v + (p * o.target2normal) * o.target2normal * o.target2factor; + else + v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor; + } + + e = o.wp02; + if(e) + { + p = e.origin + timestep * e.velocity; + if(flags & PROJECT_ON_TARGET3NORMAL) + v = v + (p * o.target3normal) * o.target3normal * o.target3factor; + else + v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor; + } + + e = o.wp03; + if(e) + { + p = e.origin + timestep * e.velocity; + if(flags & PROJECT_ON_TARGET4NORMAL) + v = v + (p * o.target4normal) * o.target4normal * o.target4factor; + else + v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor; + } + + return v; +} + +void func_vectormamamam_controller_think(entity this) +{ + this.nextthink = time + vectormamamam_timestep; + + if(this.owner.active != ACTIVE_ACTIVE) + { + this.owner.velocity = '0 0 0'; + return; + } + + if(this.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed + this.owner.velocity = (this.owner.destvec + func_vectormamamam_origin(this.owner, vectormamamam_timestep) - this.owner.origin) * 10; +} + +void func_vectormamamam_findtarget(entity this) +{ + if(this.target != "") + this.wp00 = find(NULL, targetname, this.target); + + if(this.target2 != "") + this.wp01 = find(NULL, targetname, this.target2); + + if(this.target3 != "") + this.wp02 = find(NULL, targetname, this.target3); + + if(this.target4 != "") + this.wp03 = find(NULL, targetname, this.target4); + + if(!this.wp00 && !this.wp01 && !this.wp02 && !this.wp03) + objerror(this, "No reference entity found, so there is nothing to move. Aborting."); + + this.destvec = this.origin - func_vectormamamam_origin(this, 0); + + entity controller; + controller = new(func_vectormamamam_controller); + controller.owner = this; + controller.nextthink = time + 1; + setthink(controller, func_vectormamamam_controller_think); +} + +void func_vectormamamam_setactive(entity this, int astate) +{ + if (astate == ACTIVE_TOGGLE) + { + if(this.active == ACTIVE_ACTIVE) + this.active = ACTIVE_NOT; + else + this.active = ACTIVE_ACTIVE; + } + else + this.active = astate; + + if(this.active == ACTIVE_NOT) + { + stopsound(this, CH_TRIGGER_SINGLE); + } + else + { + if(this.noise && this.noise != "") + { + _sound(this, CH_TRIGGER_SINGLE, this.noise, VOL_BASE, ATTEN_IDLE); + } + } +} + +void func_vectormamamam_init_for_player(entity this, entity player) +{ + if (this.noise && this.noise != "" && this.active == ACTIVE_ACTIVE && IS_REAL_CLIENT(player)) + { + msg_entity = player; + soundto(MSG_ONE, this, CH_TRIGGER_SINGLE, this.noise, VOL_BASE, ATTEN_IDLE); + } +} + +spawnfunc(func_vectormamamam) +{ + if (this.noise != "") + { + precache_sound(this.noise); + } + + if(!this.targetfactor) + this.targetfactor = 1; + + if(!this.target2factor) + this.target2factor = 1; + + if(!this.target3factor) + this.target3factor = 1; + + if(!this.target4factor) + this.target4factor = 1; + + if(this.targetnormal) + this.targetnormal = normalize(this.targetnormal); + + if(this.target2normal) + this.target2normal = normalize(this.target2normal); + + if(this.target3normal) + this.target3normal = normalize(this.target3normal); + + if(this.target4normal) + this.target4normal = normalize(this.target4normal); + + setblocked(this, generic_plat_blocked); + if(this.dmg && (this.message == "")) + this.message = " was squished"; + if(this.dmg && (this.message == "")) + this.message2 = "was squished by"; + if(this.dmg && (!this.dmgtime)) + this.dmgtime = 0.25; + this.dmgtime2 = time; + + if (!InitMovingBrushTrigger(this)) + return; + + // wait for targets to spawn + this.nextthink = this.ltime + 999999999; + setthink(this, SUB_NullThink); // for PushMove + + // Savage: Reduce bandwith, critical on e.g. nexdm02 + this.effects |= EF_LOWPRECISION; + + this.setactive = func_vectormamamam_setactive; + this.setactive(this, ACTIVE_ACTIVE); + + // maybe send sound to new players + IL_PUSH(g_initforplayer, this); + this.init_for_player = func_vectormamamam_init_for_player; + + InitializeEntity(this, func_vectormamamam_findtarget, INITPRIO_FINDTARGET); +} +#endif diff --git a/qcsrc/common/mapobjects/func/vectormamamam.qh b/qcsrc/common/mapobjects/func/vectormamamam.qh new file mode 100644 index 000000000..7eb6b0a63 --- /dev/null +++ b/qcsrc/common/mapobjects/func/vectormamamam.qh @@ -0,0 +1,9 @@ +#pragma once + + +const int PROJECT_ON_TARGETNORMAL = BIT(0); +const int PROJECT_ON_TARGET2NORMAL = BIT(1); +const int PROJECT_ON_TARGET3NORMAL = BIT(2); +const int PROJECT_ON_TARGET4NORMAL = BIT(3); + +const float vectormamamam_timestep = 0.1; diff --git a/qcsrc/common/mapobjects/misc/_mod.inc b/qcsrc/common/mapobjects/misc/_mod.inc new file mode 100644 index 000000000..498f6c521 --- /dev/null +++ b/qcsrc/common/mapobjects/misc/_mod.inc @@ -0,0 +1,6 @@ +// generated file; do not modify +#include +#include +#include +#include +#include diff --git a/qcsrc/common/mapobjects/misc/_mod.qh b/qcsrc/common/mapobjects/misc/_mod.qh new file mode 100644 index 000000000..3415919f8 --- /dev/null +++ b/qcsrc/common/mapobjects/misc/_mod.qh @@ -0,0 +1,6 @@ +// generated file; do not modify +#include +#include +#include +#include +#include diff --git a/qcsrc/common/mapobjects/misc/corner.qc b/qcsrc/common/mapobjects/misc/corner.qc new file mode 100644 index 000000000..a0f67b759 --- /dev/null +++ b/qcsrc/common/mapobjects/misc/corner.qc @@ -0,0 +1,75 @@ +#include "corner.qh" +REGISTER_NET_LINKED(ENT_CLIENT_CORNER) + +#ifdef SVQC +bool corner_send(entity this, entity to, int sf) +{ + WriteHeader(MSG_ENTITY, ENT_CLIENT_CORNER); + + WriteString(MSG_ENTITY, this.platmovetype); + + WriteVector(MSG_ENTITY, this.origin); + + WriteString(MSG_ENTITY, this.target); + WriteString(MSG_ENTITY, this.target2); + WriteString(MSG_ENTITY, this.target3); + WriteString(MSG_ENTITY, this.target4); + WriteString(MSG_ENTITY, this.targetname); + WriteByte(MSG_ENTITY, this.target_random); + + WriteByte(MSG_ENTITY, this.wait); + + return true; +} + +void corner_link(entity this) +{ + //Net_LinkEntity(this, false, 0, corner_send); +} + +spawnfunc(path_corner) +{ + // setup values for overriding train movement + // if a second value does not exist, both start and end speeds are the single value specified + set_platmovetype(this, this.platmovetype); + + corner_link(this); +} +#elif defined(CSQC) + +void corner_remove(entity this) +{ + strfree(this.target); + strfree(this.target2); + strfree(this.target3); + strfree(this.target4); + strfree(this.targetname); + strfree(this.platmovetype); +} + +NET_HANDLE(ENT_CLIENT_CORNER, bool isnew) +{ + this.platmovetype = strzone(ReadString()); + + this.origin = ReadVector(); + setorigin(this, this.origin); + + this.target = strzone(ReadString()); + this.target2 = strzone(ReadString()); + this.target3 = strzone(ReadString()); + this.target4 = strzone(ReadString()); + this.targetname = strzone(ReadString()); + this.target_random = ReadByte(); + + this.wait = ReadByte(); + + return = true; + + this.classname = "path_corner"; + this.drawmask = MASK_NORMAL; + this.entremove = corner_remove; + + set_platmovetype(this, this.platmovetype); +} + +#endif diff --git a/qcsrc/common/mapobjects/misc/corner.qh b/qcsrc/common/mapobjects/misc/corner.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/misc/corner.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/misc/dynlight.qc b/qcsrc/common/mapobjects/misc/dynlight.qc new file mode 100644 index 000000000..7c70b8444 --- /dev/null +++ b/qcsrc/common/mapobjects/misc/dynlight.qc @@ -0,0 +1,135 @@ +#include "dynlight.qh" + +#ifdef SVQC +#include +#include + +const float LOOP = 1; + +.float speed; + +//const int DNOSHADOW = 2; +const int DFOLLOW = 4; +.float light_lev; +.float lefty; +.vector color; +.string dtagname; + +/*QUAKED dynlight (0 1 0) (-8 -8 -8) (8 8 8) START_OFF NOSHADOW FOLLOW +Dynamic spawnfunc_light. +Can do one of these things: sit still and be just a silly spawnfunc_light, travel along a path, follow an entity around, attach to a tag on an entity. +It can spin around it's own axis in all the above cases. +If targeted, it will toggle between on or off. +keys: +"light_lev" spawnfunc_light radius, default 200 +"color" spawnfunc_light color in rgb and brightness, 1 1 1 produces bright white, up to 255 255 255 (nuclear blast), recommended values up to 1 1 1, default 1 1 1 +"style" lightstyle, same as for static lights +"angles" initial orientation +"avelocity" a vector value, the direction and speed it rotates in +"skin" cubemap number, must be 16 or above +"dtagname" will attach to this tag on the entity which "targetname" matches "target". If the "target" is either not an md3 model or is missing tags, it will attach to the targets origin. Note that the "target" must be visible to the spawnfunc_light +"targetname" will toggle on and off when triggered +"target" if issued with a target, preferrably spawnfunc_path_corner, it will move along the path. If also issued with the FOLLOW spawnflag, then this is the entity it will follow. If issued with the "tagname" key it will attach it to this targets tag called "tagname", does not work together with FOLLOW or path movement +"speed" the speed it will travel along the path, default 100 +flags: +"START_OFF" spawnfunc_light will be in off state until targeted +"NOSHADOW" will not cast shadows in realtime lighting mode +"FOLLOW" will follow the entity which "targetname" matches "target" +*/ +void dynlight_think(entity this) +{ + if(!this.owner) + delete(this); + + this.nextthink = time + 0.1; +} +void dynlight_find_aiment(entity this) +{ + entity targ; + if (!this.target) + objerror (this, "dynlight: no target to follow"); + + targ = find(NULL, targetname, this.target); + set_movetype(this, MOVETYPE_FOLLOW); + this.aiment = targ; + this.owner = targ; + this.punchangle = targ.angles; + this.view_ofs = this.origin - targ.origin; + this.v_angle = this.angles - targ.angles; + setthink(this, dynlight_think); + this.nextthink = time + 0.1; +} +void dynlight_find_path(entity this) +{ + entity targ; + if (!this.target) + objerror (this, "dynlight: no target to follow"); + + targ = find(NULL, targetname, this.target); + this.target = targ.target; + setorigin(this, targ.origin); + setthink(this, train_next); // TODO: reliant on the train's pathing functions + this.nextthink = time + 0.1; +} +void dynlight_find_target(entity this) +{ + entity targ; + if (!this.target) + objerror (this, "dynlight: no target to follow"); + + targ = find(NULL, targetname, this.target); + setattachment(this, targ, this.dtagname); + this.owner = targ; + setthink(this, dynlight_think); + this.nextthink = time + 0.1; +} +void dynlight_use(entity this, entity actor, entity trigger) +{ + if (this.light_lev == 0) + this.light_lev = this.lefty; + else + this.light_lev = 0; +} +spawnfunc(dynlight) +{ + if (!this.light_lev) + this.light_lev = 200; + if (!this.color) + this.color = '1 1 1'; + this.lefty = this.light_lev; + this.use = dynlight_use; + setsize (this, '0 0 0', '0 0 0'); + setorigin(this, this.origin); + //this.pflags = PFLAGS_FULLDYNAMIC; + this.solid = SOLID_NOT; + //this.blocked = func_null; + //if (this.spawnflags & DNOSHADOW) + // this.pflags = this.pflags + PFLAGS_NOSHADOW; + //if (this.spawnflags & START_OFF) + // this.light_lev = 0; + +//tag attaching + if (this.dtagname) + { + InitializeEntity(this, dynlight_find_target, INITPRIO_FINDTARGET); + return; + } + +// entity following + if (this.spawnflags & DFOLLOW) + { + InitializeEntity(this, dynlight_find_aiment, INITPRIO_FINDTARGET); + return; + } +// path following + if (this.target) +// if (!(this.spawnflags & DFOLLOW)) + { + set_movetype(this, MOVETYPE_NOCLIP); + if (!this.speed) + this.speed = 100; + InitializeEntity(this, dynlight_find_path, INITPRIO_FINDTARGET); + return; + } +} +#endif diff --git a/qcsrc/common/mapobjects/misc/dynlight.qh b/qcsrc/common/mapobjects/misc/dynlight.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/misc/dynlight.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/misc/follow.qc b/qcsrc/common/mapobjects/misc/follow.qc new file mode 100644 index 000000000..87619ca71 --- /dev/null +++ b/qcsrc/common/mapobjects/misc/follow.qc @@ -0,0 +1,70 @@ +#include "follow.qh" +// the way this entity works makes it no use to CSQC, as it removes itself instantly + +#ifdef SVQC +void follow_init(entity this) +{ + entity src, dst; + src = NULL; + dst = NULL; + if(this.killtarget != "") + src = find(NULL, targetname, this.killtarget); + if(this.target != "") + dst = find(NULL, targetname, this.target); + + if(!src && !dst) + { + objerror(this, "follow: could not find target/killtarget"); + return; + } + + if(this.jointtype) + { + // already done :P entity must stay + this.aiment = src; + this.enemy = dst; + } + else if(!src || !dst) + { + objerror(this, "follow: could not find target/killtarget"); + return; + } + else if(this.spawnflags & FOLLOW_ATTACH) + { + // attach + if(this.spawnflags & FOLLOW_LOCAL) + { + setattachment(dst, src, this.message); + } + else + { + attach_sameorigin(dst, src, this.message); + } + + dst.solid = SOLID_NOT; // solid doesn't work with attachment + delete(this); + } + else + { + if(this.spawnflags & FOLLOW_LOCAL) + { + set_movetype(dst, MOVETYPE_FOLLOW); + dst.aiment = src; + // dst.punchangle = '0 0 0'; // keep unchanged + dst.view_ofs = dst.origin; + dst.v_angle = dst.angles; + } + else + { + follow_sameorigin(dst, src); + } + + delete(this); + } +} + +spawnfunc(misc_follow) +{ + InitializeEntity(this, follow_init, INITPRIO_FINDTARGET); +} +#endif diff --git a/qcsrc/common/mapobjects/misc/follow.qh b/qcsrc/common/mapobjects/misc/follow.qh new file mode 100644 index 000000000..aef491ff3 --- /dev/null +++ b/qcsrc/common/mapobjects/misc/follow.qh @@ -0,0 +1,5 @@ +#pragma once + + +const int FOLLOW_ATTACH = BIT(0); +const int FOLLOW_LOCAL = BIT(1); diff --git a/qcsrc/common/mapobjects/misc/laser.qc b/qcsrc/common/mapobjects/misc/laser.qc new file mode 100644 index 000000000..df88b750f --- /dev/null +++ b/qcsrc/common/mapobjects/misc/laser.qc @@ -0,0 +1,418 @@ +#include "laser.qh" +#if defined(CSQC) + #include + #include + #include +#elif defined(MENUQC) +#elif defined(SVQC) +#endif + +REGISTER_NET_LINKED(ENT_CLIENT_LASER) + +#ifdef SVQC +.float modelscale; +void misc_laser_aim(entity this) +{ + vector a; + if(this.enemy) + { + if(this.spawnflags & LASER_FINITE) + { + if(this.enemy.origin != this.mangle) + { + this.mangle = this.enemy.origin; + this.SendFlags |= SF_LASER_UPDATE_TARGET; + } + } + else + { + a = vectoangles(this.enemy.origin - this.origin); + a_x = -a_x; + if(a != this.mangle) + { + this.mangle = a; + this.SendFlags |= SF_LASER_UPDATE_TARGET; + } + } + } + else + { + if(this.angles != this.mangle) + { + this.mangle = this.angles; + this.SendFlags |= SF_LASER_UPDATE_TARGET; + } + } + if(this.origin != this.oldorigin) + { + this.SendFlags |= SF_LASER_UPDATE_ORIGIN; + this.oldorigin = this.origin; + } +} + +void misc_laser_init(entity this) +{ + if(this.target != "") + this.enemy = find(NULL, targetname, this.target); +} + +.entity pusher; +void misc_laser_think(entity this) +{ + vector o; + entity hitent; + vector hitloc; + + this.nextthink = time; + + if(this.active == ACTIVE_NOT) + return; + + misc_laser_aim(this); + + if(this.enemy) + { + o = this.enemy.origin; + if (!(this.spawnflags & LASER_FINITE)) + o = this.origin + normalize(o - this.origin) * LASER_BEAM_MAXLENGTH; + } + else + { + makevectors(this.mangle); + o = this.origin + v_forward * LASER_BEAM_MAXLENGTH; + } + + if(this.dmg || this.enemy.target != "") + { + traceline(this.origin, o, MOVE_NORMAL, this); + } + hitent = trace_ent; + hitloc = trace_endpos; + + if(this.enemy.target != "") // DETECTOR laser + { + if(trace_ent.iscreature) + { + this.pusher = hitent; + if(!this.count) + { + this.count = 1; + + SUB_UseTargets(this.enemy, this.enemy.pusher, NULL); + } + } + else + { + if(this.count) + { + this.count = 0; + + SUB_UseTargets(this.enemy, this.enemy.pusher, NULL); + } + } + } + + if(this.dmg) + { + if(this.team) + if(((this.spawnflags & LASER_INVERT_TEAM) == 0) == (this.team != hitent.team)) + return; + if(hitent.takedamage) + Damage(hitent, this, this, ((this.dmg < 0) ? 100000 : (this.dmg * frametime)), DEATH_HURTTRIGGER.m_id, DMG_NOWEP, hitloc, '0 0 0'); + } +} + +bool laser_SendEntity(entity this, entity to, float sendflags) +{ + WriteHeader(MSG_ENTITY, ENT_CLIENT_LASER); + sendflags = sendflags & 0x0F; // use that bit to indicate finite length laser + if(this.spawnflags & LASER_FINITE) + sendflags |= SF_LASER_FINITE; + if(this.alpha) + sendflags |= SF_LASER_ALPHA; + if(this.scale != 1 || this.modelscale != 1) + sendflags |= SF_LASER_SCALE; + if(this.spawnflags & LASER_NOTRACE) + sendflags |= SF_LASER_NOTRACE; + WriteByte(MSG_ENTITY, sendflags); + if(sendflags & SF_LASER_UPDATE_ORIGIN) + { + WriteVector(MSG_ENTITY, this.origin); + } + if(sendflags & SF_LASER_UPDATE_EFFECT) + { + WriteByte(MSG_ENTITY, this.beam_color.x * 255.0); + WriteByte(MSG_ENTITY, this.beam_color.y * 255.0); + WriteByte(MSG_ENTITY, this.beam_color.z * 255.0); + if(sendflags & SF_LASER_ALPHA) + WriteByte(MSG_ENTITY, this.alpha * 255.0); + if(sendflags & SF_LASER_SCALE) + { + WriteByte(MSG_ENTITY, bound(0, this.scale * 16.0, 255)); + WriteByte(MSG_ENTITY, bound(0, this.modelscale * 16.0, 255)); + } + if((sendflags & SF_LASER_FINITE) || !(sendflags & SF_LASER_NOTRACE)) // effect doesn't need sending if the laser is infinite and has collision testing turned off + WriteShort(MSG_ENTITY, this.cnt); + } + if(sendflags & SF_LASER_UPDATE_TARGET) + { + if(sendflags & SF_LASER_FINITE) + { + WriteVector(MSG_ENTITY, this.enemy.origin); + } + else + { + WriteAngle(MSG_ENTITY, this.mangle_x); + WriteAngle(MSG_ENTITY, this.mangle_y); + } + } + if(sendflags & SF_LASER_UPDATE_ACTIVE) + WriteByte(MSG_ENTITY, this.active); + return true; +} + +/*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED +Any object touching the beam will be hurt +Keys: +"target" + spawnfunc_target_position where the laser ends +"mdl" + name of beam end effect to use +"beam_color" + color of the beam (default: red) +"dmg" + damage per second (-1 for a laser that kills immediately) +*/ + +void laser_setactive(entity this, int act) +{ + int old_status = this.active; + if(act == ACTIVE_TOGGLE) + { + if(this.active == ACTIVE_ACTIVE) + { + this.active = ACTIVE_NOT; + } + else + { + this.active = ACTIVE_ACTIVE; + } + } + else + { + this.active = act; + } + + if (this.active != old_status) + { + this.SendFlags |= SF_LASER_UPDATE_ACTIVE; + misc_laser_aim(this); + } +} + +void laser_use(entity this, entity actor, entity trigger) +{ + this.setactive(this, ACTIVE_TOGGLE); +} + +spawnfunc(misc_laser) +{ + if(this.mdl) + { + if(this.mdl == "none") + this.cnt = -1; + else + { + this.cnt = _particleeffectnum(this.mdl); + if(this.cnt < 0 && this.dmg) + this.cnt = particleeffectnum(EFFECT_LASER_DEADLY); + } + } + else if(!this.cnt) + { + if(this.dmg) + this.cnt = particleeffectnum(EFFECT_LASER_DEADLY); + else + this.cnt = -1; + } + if(this.cnt < 0) + this.cnt = -1; + + if(!this.beam_color && this.colormod) + { + LOG_WARN("misc_laser uses legacy field 'colormod', please use 'beam_color' instead"); + this.beam_color = this.colormod; + } + + if(this.beam_color == '0 0 0') + { + if(!this.alpha) + this.beam_color = '1 0 0'; + } + + if(this.message == "") + { + this.message = "saw the light"; + } + if (this.message2 == "") + { + this.message2 = "was pushed into a laser by"; + } + if(!this.scale) + { + this.scale = 1; + } + if(!this.modelscale) + { + this.modelscale = 1; + } + else if(this.modelscale < 0) + { + this.modelscale = 0; + } + setthink(this, misc_laser_think); + this.nextthink = time; + InitializeEntity(this, misc_laser_init, INITPRIO_FINDTARGET); + + this.mangle = this.angles; + + Net_LinkEntity(this, false, 0, laser_SendEntity); + + this.setactive = laser_setactive; + + IFTARGETED + { + // backwards compatibility + this.use = laser_use; + } + + this.reset = generic_netlinked_reset; + this.reset(this); +} +#elif defined(CSQC) + +// a laser goes from origin in direction angles +// it has color 'beam_color' +// and stops when something is in the way +entityclass(Laser); +classfield(Laser) .int cnt; // end effect +classfield(Laser) .vector colormod; +classfield(Laser) .int state; // on-off +classfield(Laser) .int count; // flags for the laser +classfield(Laser) .vector velocity; // laser endpoint if it is FINITE +classfield(Laser) .float alpha; +classfield(Laser) .float scale; // scaling factor of the thickness +classfield(Laser) .float modelscale; // scaling factor of the dlight + +void Draw_Laser(entity this) +{ + if(this.active == ACTIVE_NOT) + return; + InterpolateOrigin_Do(this); + if(this.count & SF_LASER_FINITE) + { + if(this.count & SF_LASER_NOTRACE) + { + trace_endpos = this.velocity; + trace_dphitq3surfaceflags = 0; + } + else + traceline(this.origin, this.velocity, 0, this); + } + else + { + if(this.count & SF_LASER_NOTRACE) + { + makevectors(this.angles); + trace_endpos = this.origin + v_forward * LASER_BEAM_MAXWORLDSIZE; + trace_dphitq3surfaceflags = Q3SURFACEFLAG_SKY; + } + else + { + makevectors(this.angles); + traceline(this.origin, this.origin + v_forward * LASER_BEAM_MAXLENGTH, 0, this); + if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY) + trace_endpos = this.origin + v_forward * LASER_BEAM_MAXWORLDSIZE; + } + } + if(this.scale != 0) + { + if(this.alpha) + { + Draw_CylindricLine(this.origin, trace_endpos, this.scale, "particles/laserbeam", 0, time * 3, this.beam_color, this.alpha, DRAWFLAG_NORMAL, view_origin); + } + else + { + Draw_CylindricLine(this.origin, trace_endpos, this.scale, "particles/laserbeam", 0, time * 3, this.beam_color, 0.5, DRAWFLAG_ADDITIVE, view_origin); + } + } + if (!(trace_dphitq3surfaceflags & (Q3SURFACEFLAG_SKY | Q3SURFACEFLAG_NOIMPACT))) + { + if(this.cnt >= 0) + __pointparticles(this.cnt, trace_endpos, trace_plane_normal, drawframetime * 1000); + if(this.beam_color != '0 0 0' && this.modelscale != 0) + adddynamiclight(trace_endpos + trace_plane_normal * 1, this.modelscale, this.beam_color * 5); + } +} + +NET_HANDLE(ENT_CLIENT_LASER, bool isnew) +{ + InterpolateOrigin_Undo(this); + + // 30 bytes, or 13 bytes for just moving + int sendflags = ReadByte(); + this.count = (sendflags & 0xF0); + + if(this.count & SF_LASER_FINITE) + this.iflags = IFLAG_VELOCITY | IFLAG_ORIGIN; + else + this.iflags = IFLAG_ANGLES | IFLAG_ORIGIN; + + if(sendflags & SF_LASER_UPDATE_ORIGIN) + { + this.origin = ReadVector(); + setorigin(this, this.origin); + } + if(sendflags & SF_LASER_UPDATE_EFFECT) + { + this.beam_color.x = ReadByte() / 255.0; + this.beam_color.y = ReadByte() / 255.0; + this.beam_color.z = ReadByte() / 255.0; + if(sendflags & SF_LASER_ALPHA) + this.alpha = ReadByte() / 255.0; + else + this.alpha = 0; + this.scale = 2; // NOTE: why 2? + this.modelscale = 50; // NOTE: why 50? + if(sendflags & SF_LASER_SCALE) + { + this.scale *= ReadByte() / 16.0; // beam radius + this.modelscale *= ReadByte() / 16.0; // dlight radius + } + if((sendflags & SF_LASER_FINITE) || !(sendflags & SF_LASER_NOTRACE)) + this.cnt = ReadShort(); // effect number + else + this.cnt = 0; + } + if(sendflags & SF_LASER_UPDATE_TARGET) + { + if(sendflags & SF_LASER_FINITE) + { + this.velocity = ReadVector(); + } + else + { + this.angles_x = ReadAngle(); + this.angles_y = ReadAngle(); + } + } + if(sendflags & SF_LASER_UPDATE_ACTIVE) + this.active = ReadByte(); + + return = true; + + InterpolateOrigin_Note(this); + this.draw = Draw_Laser; + if (isnew) IL_PUSH(g_drawables, this); +} +#endif diff --git a/qcsrc/common/mapobjects/misc/laser.qh b/qcsrc/common/mapobjects/misc/laser.qh new file mode 100644 index 000000000..0ff57646a --- /dev/null +++ b/qcsrc/common/mapobjects/misc/laser.qh @@ -0,0 +1,22 @@ +#pragma once + + +const int LASER_FINITE = BIT(1); +const int LASER_NOTRACE = BIT(2); +const int LASER_INVERT_TEAM = BIT(3); + +const int SF_LASER_UPDATE_ORIGIN = BIT(0); +const int SF_LASER_UPDATE_TARGET = BIT(1); +const int SF_LASER_UPDATE_ACTIVE = BIT(2); +const int SF_LASER_UPDATE_EFFECT = BIT(3); + +const int SF_LASER_NOTRACE = BIT(4); +const int SF_LASER_SCALE = BIT(5); +const int SF_LASER_ALPHA = BIT(6); +const int SF_LASER_FINITE = BIT(7); + +.vector beam_color; + +const float LASER_BEAM_MAXLENGTH = 32768; // maximum length of a beam trace +// TODO: find a better way to do this +const float LASER_BEAM_MAXWORLDSIZE = 1048576; // to make sure the endpoint of the beam is not visible inside diff --git a/qcsrc/common/mapobjects/misc/teleport_dest.qc b/qcsrc/common/mapobjects/misc/teleport_dest.qc new file mode 100644 index 000000000..126a20ea2 --- /dev/null +++ b/qcsrc/common/mapobjects/misc/teleport_dest.qc @@ -0,0 +1,89 @@ +#include "teleport_dest.qh" +REGISTER_NET_LINKED(ENT_CLIENT_TELEPORT_DEST) + +#ifdef SVQC + +bool teleport_dest_send(entity this, entity to, int sendflags) +{ + WriteHeader(MSG_ENTITY, ENT_CLIENT_TELEPORT_DEST); + WriteByte(MSG_ENTITY, sendflags); + + if(sendflags & SF_TRIGGER_INIT) + { + WriteByte(MSG_ENTITY, this.cnt); + WriteCoord(MSG_ENTITY, this.speed); + WriteString(MSG_ENTITY, this.targetname); + WriteVector(MSG_ENTITY, this.origin); + + WriteAngle(MSG_ENTITY, this.mangle_x); + WriteAngle(MSG_ENTITY, this.mangle_y); + WriteAngle(MSG_ENTITY, this.mangle_z); + } + + return true; +} + +void teleport_dest_link(entity this) +{ + Net_LinkEntity(this, false, 0, teleport_dest_send); + this.SendFlags |= SF_TRIGGER_INIT; +} + +spawnfunc(info_teleport_destination) +{ + this.classname = "info_teleport_destination"; + + this.mangle = this.angles; + this.angles = '0 0 0'; + + //setorigin(this, this.origin + '0 0 27'); // To fix a mappers' habit as old as Quake + setorigin(this, this.origin); + + IFTARGETED + { + } + else + objerror (this, "^3Teleport destination without a targetname"); + + teleport_dest_link(this); +} + +spawnfunc(misc_teleporter_dest) +{ + spawnfunc_info_teleport_destination(this); +} + +#elif defined(CSQC) + +void teleport_dest_remove(entity this) +{ + // strfree(this.classname); + strfree(this.targetname); +} + +NET_HANDLE(ENT_CLIENT_TELEPORT_DEST, bool isnew) +{ + int sendflags = ReadByte(); + + if(sendflags & SF_TRIGGER_INIT) + { + this.classname = "info_teleport_destination"; + this.cnt = ReadByte(); + this.speed = ReadCoord(); + this.targetname = strzone(ReadString()); + this.origin = ReadVector(); + + this.mangle_x = ReadAngle(); + this.mangle_y = ReadAngle(); + this.mangle_z = ReadAngle(); + + setorigin(this, this.origin); + + this.drawmask = MASK_NORMAL; + this.entremove = teleport_dest_remove; + } + + return = true; +} + +#endif diff --git a/qcsrc/common/mapobjects/misc/teleport_dest.qh b/qcsrc/common/mapobjects/misc/teleport_dest.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/misc/teleport_dest.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/models.qc b/qcsrc/common/mapobjects/models.qc new file mode 100644 index 000000000..92ff464b7 --- /dev/null +++ b/qcsrc/common/mapobjects/models.qc @@ -0,0 +1,413 @@ +#include "models.qh" + +#ifdef SVQC +#include +#include +#include +#include "subs.qh" +#include "triggers.qh" + +entityclass(BGMScript); +classfield(BGMScript) .string bgmscript; +classfield(BGMScript) .float bgmscriptattack; +classfield(BGMScript) .float bgmscriptdecay; +classfield(BGMScript) .float bgmscriptsustain; +classfield(BGMScript) .float bgmscriptrelease; + +#include +#include "../../lib/csqcmodel/sv_model.qh" + +.float modelscale; + +void g_model_setcolormaptoactivator(entity this, entity actor, entity trigger) +{ + if(teamplay) + { + if(actor.team) + this.colormap = (actor.team - 1) * 0x11; + else + this.colormap = 0x00; + } + else + this.colormap = floor(random() * 256); + this.colormap |= BIT(10); // RENDER_COLORMAPPED +} + +void g_clientmodel_setcolormaptoactivator(entity this, entity actor, entity trigger) +{ + g_model_setcolormaptoactivator(this, actor, trigger); + this.SendFlags |= (BIT(3) | BIT(0)); +} + +void g_clientmodel_use(entity this, entity actor, entity trigger) +{ + if (this.antiwall_flag == 1) + { + this.inactive = 1; + this.solid = SOLID_NOT; + } + else if (this.antiwall_flag == 2) + { + this.inactive = 0; + this.solid = this.default_solid; + } + g_clientmodel_setcolormaptoactivator(this, actor, trigger); +} + +void g_model_dropbyspawnflags(entity this) +{ + if((this.spawnflags & 3) == 1) // ALIGN_ORIGIN + { + traceline(this.origin, this.origin - '0 0 4096', MOVE_NOMONSTERS, this); + setorigin(this, trace_endpos); + } + else if((this.spawnflags & 3) == 2) // ALIGN_BOTTOM + { + tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 4096', MOVE_NOMONSTERS, this); + setorigin(this, trace_endpos); + } + else if((this.spawnflags & 3) == 3) // ALIGN_ORIGIN | ALIGN_BOTTOM + { + traceline(this.origin, this.origin - '0 0 4096', MOVE_NOMONSTERS, this); + setorigin(this, trace_endpos - '0 0 1' * this.mins.z); + } +} + +void g_clientmodel_dropbyspawnflags(entity this) +{ + vector o0; + o0 = this.origin; + g_model_dropbyspawnflags(this); + if(this.origin != o0) + this.SendFlags |= 2; +} + +bool g_clientmodel_genericsendentity(entity this, entity to, int sf) +{ + sf = sf & 0x0F; + if(this.angles != '0 0 0') + sf |= 0x10; + if(this.mins != '0 0 0' || this.maxs != '0 0 0') + sf |= 0x20; + if(this.colormap != 0) + sf |= 0x40; + if(this.lodmodelindex1) + sf |= 0x80; + + WriteHeader(MSG_ENTITY, ENT_CLIENT_WALL); + WriteByte(MSG_ENTITY, sf); + + if(sf & BIT(0)) + { + if(sf & 0x40) + WriteShort(MSG_ENTITY, this.colormap); + WriteByte(MSG_ENTITY, this.skin); + } + + if(sf & BIT(1)) + { + WriteVector(MSG_ENTITY, this.origin); + } + + if(sf & BIT(2)) + { + if(sf & 0x10) + { + WriteAngle(MSG_ENTITY, this.angles.x); + WriteAngle(MSG_ENTITY, this.angles.y); + WriteAngle(MSG_ENTITY, this.angles.z); + } + } + + if(sf & BIT(3)) + { + if(sf & 0x80) + { + WriteShort(MSG_ENTITY, this.lodmodelindex0); + WriteShort(MSG_ENTITY, bound(0, this.loddistance1, 65535)); + WriteShort(MSG_ENTITY, this.lodmodelindex1); + WriteShort(MSG_ENTITY, bound(0, this.loddistance2, 65535)); + WriteShort(MSG_ENTITY, this.lodmodelindex2); + } + else + WriteShort(MSG_ENTITY, this.modelindex); + WriteByte(MSG_ENTITY, this.solid); + WriteShort(MSG_ENTITY, floor(this.scale * 256)); + if(sf & 0x20) + { + WriteVector(MSG_ENTITY, this.mins); + WriteVector(MSG_ENTITY, this.maxs); + } + WriteString(MSG_ENTITY, this.bgmscript); + if(this.bgmscript != "") + { + WriteByte(MSG_ENTITY, floor(this.bgmscriptattack * 64)); + WriteByte(MSG_ENTITY, floor(this.bgmscriptdecay * 64)); + WriteByte(MSG_ENTITY, floor(this.bgmscriptsustain * 255)); + WriteByte(MSG_ENTITY, floor(this.bgmscriptrelease * 64)); + WriteVector(MSG_ENTITY, this.movedir); + WriteByte(MSG_ENTITY, floor(this.lip * 255)); + } + WriteByte(MSG_ENTITY, this.fade_start); + WriteByte(MSG_ENTITY, this.fade_end); + WriteByte(MSG_ENTITY, this.alpha_max); + WriteByte(MSG_ENTITY, this.alpha_min); + WriteByte(MSG_ENTITY, this.inactive); + WriteShort(MSG_ENTITY, this.fade_vertical_offset); + } + + return true; +} + + +#define G_MODEL_INIT(ent,sol) \ + if(ent.geomtype) if(autocvar_physics_ode && checkextension("DP_PHYSICS_ODE")) set_movetype(ent, MOVETYPE_PHYSICS); \ + if(!ent.scale) ent.scale = ent.modelscale; \ + SetBrushEntityModel(ent); \ + ent.use = g_model_setcolormaptoactivator; \ + InitializeEntity(ent, g_model_dropbyspawnflags, INITPRIO_DROPTOFLOOR); \ + if(!ent.solid) ent.solid = (sol); else if(ent.solid < 0) ent.solid = SOLID_NOT; + +#define G_CLIENTMODEL_INIT(ent,sol) \ + if(ent.geomtype) if(autocvar_physics_ode && checkextension("DP_PHYSICS_ODE")) set_movetype(ent, MOVETYPE_PHYSICS); \ + if(!ent.scale) ent.scale = ent.modelscale; \ + SetBrushEntityModel(ent); \ + ent.use = g_clientmodel_use; \ + InitializeEntity(ent, g_clientmodel_dropbyspawnflags, INITPRIO_DROPTOFLOOR); \ + if(!ent.solid) ent.solid = (sol); else if(ent.solid < 0) ent.solid = SOLID_NOT; \ + if(!ent.bgmscriptsustain) ent.bgmscriptsustain = 1; else if(ent.bgmscriptsustain < 0) ent.bgmscriptsustain = 0; \ + Net_LinkEntity(ent, true, 0, g_clientmodel_genericsendentity); \ + ent.default_solid = sol; + +// non-solid model entities: +spawnfunc(misc_gamemodel) { this.angles_x = -this.angles.x; G_MODEL_INIT (this, SOLID_NOT) } // model entity +spawnfunc(misc_clientmodel) { this.angles_x = -this.angles.x; G_CLIENTMODEL_INIT(this, SOLID_NOT) } // model entity +spawnfunc(misc_models) { this.angles_x = -this.angles.x; G_MODEL_INIT (this, SOLID_NOT) } // DEPRECATED old compat entity with confusing name, do not use + +// non-solid brush entities: +spawnfunc(func_illusionary) { G_MODEL_INIT (this, SOLID_NOT) } // Q1 name (WARNING: MISPREDICTED) +spawnfunc(func_clientillusionary) { G_CLIENTMODEL_INIT(this, SOLID_NOT) } // brush entity +spawnfunc(func_static) { G_MODEL_INIT (this, SOLID_NOT) } // DEPRECATED old alias name from some other game + +// solid brush entities +spawnfunc(func_wall) { G_MODEL_INIT (this, SOLID_BSP) } // Q1 name +spawnfunc(func_clientwall) { G_CLIENTMODEL_INIT(this, SOLID_BSP) } // brush entity (WARNING: MISPREDICTED) +#elif defined(CSQC) +.float alpha; +.float scale; +.vector movedir; + +void Ent_Wall_PreDraw(entity this) +{ + if (this.inactive) + { + this.alpha = 0; + } + else + { + vector org = getpropertyvec(VF_ORIGIN); + if(!checkpvs(org, this)) + this.alpha = 0; + else if(this.fade_start || this.fade_end) { + vector offset = '0 0 0'; + offset_z = this.fade_vertical_offset; + float player_dist = vlen(org - this.origin - 0.5 * (this.mins + this.maxs) + offset); + if (this.fade_end == this.fade_start) + { + if (player_dist >= this.fade_start) + this.alpha = 0; + else + this.alpha = 1; + } + else + { + this.alpha = (this.alpha_min + this.alpha_max * bound(0, + (this.fade_end - player_dist) + / (this.fade_end - this.fade_start), 1)) / 100.0; + } + } + else + { + this.alpha = 1; + } + } + if(this.alpha <= 0) + this.drawmask = 0; + else + this.drawmask = MASK_NORMAL; +} + +void Ent_Wall_Draw(entity this) +{ + float f; + var .vector fld; + + if(this.bgmscriptangular) + fld = angles; + else + fld = origin; + this.(fld) = this.saved; + + if(this.lodmodelindex1) + { + if(autocvar_cl_modeldetailreduction <= 0) + { + if(this.lodmodelindex2 && autocvar_cl_modeldetailreduction <= -2) + this.modelindex = this.lodmodelindex2; + else if(autocvar_cl_modeldetailreduction <= -1) + this.modelindex = this.lodmodelindex1; + else + this.modelindex = this.lodmodelindex0; + } + else + { + float distance = vlen(NearestPointOnBox(this, view_origin) - view_origin); + f = (distance * current_viewzoom + 100.0) * autocvar_cl_modeldetailreduction; + f *= 1.0 / bound(0.01, view_quality, 1); + if(this.lodmodelindex2 && f > this.loddistance2) + this.modelindex = this.lodmodelindex2; + else if(f > this.loddistance1) + this.modelindex = this.lodmodelindex1; + else + this.modelindex = this.lodmodelindex0; + } + } + + InterpolateOrigin_Do(this); + + this.saved = this.(fld); + + f = doBGMScript(this); + if(f >= 0) + { + if(this.lip < 0) // < 0: alpha goes from 1 to 1-|lip| when toggled (toggling subtracts lip) + this.alpha = 1 + this.lip * f; + else // > 0: alpha goes from 1-|lip| to 1 when toggled (toggling adds lip) + this.alpha = 1 - this.lip * (1 - f); + this.(fld) = this.(fld) + this.movedir * f; + } + else + this.alpha = 1; + + if(this.alpha >= ALPHA_MIN_VISIBLE) + this.drawmask = MASK_NORMAL; + else + this.drawmask = 0; +} + +void Ent_Wall_Remove(entity this) +{ + strfree(this.bgmscript); +} + +NET_HANDLE(ENT_CLIENT_WALL, bool isnew) +{ + int f; + var .vector fld; + + InterpolateOrigin_Undo(this); + this.iflags = IFLAG_ANGLES | IFLAG_ORIGIN; + + if(this.bgmscriptangular) + fld = angles; + else + fld = origin; + this.(fld) = this.saved; + + f = ReadByte(); + + if(f & 1) + { + if(f & 0x40) + this.colormap = ReadShort(); + else + this.colormap = 0; + this.skin = ReadByte(); + } + + if(f & 2) + { + this.origin = ReadVector(); + setorigin(this, this.origin); + } + + if(f & 4) + { + if(f & 0x10) + { + this.angles_x = ReadAngle(); + this.angles_y = ReadAngle(); + this.angles_z = ReadAngle(); + } + else + this.angles = '0 0 0'; + } + + if(f & 8) + { + if(f & 0x80) + { + this.lodmodelindex0 = ReadShort(); + this.loddistance1 = ReadShort(); + this.lodmodelindex1 = ReadShort(); + this.loddistance2 = ReadShort(); + this.lodmodelindex2 = ReadShort(); + } + else + { + this.modelindex = ReadShort(); + this.loddistance1 = 0; + this.loddistance2 = 0; + } + this.solid = ReadByte(); + this.scale = ReadShort() / 256.0; + if(f & 0x20) + { + this.mins = ReadVector(); + this.maxs = ReadVector(); + } + else + this.mins = this.maxs = '0 0 0'; + setsize(this, this.mins, this.maxs); + + string s = ReadString(); + if(substring(s, 0, 1) == "<") + { + strcpy(this.bgmscript, substring(s, 1, -1)); + this.bgmscriptangular = 1; + } + else + { + strcpy(this.bgmscript, s); + this.bgmscriptangular = 0; + } + if(this.bgmscript != "") + { + this.bgmscriptattack = ReadByte() / 64.0; + this.bgmscriptdecay = ReadByte() / 64.0; + this.bgmscriptsustain = ReadByte() / 255.0; + this.bgmscriptrelease = ReadByte() / 64.0; + this.movedir = ReadVector(); + this.lip = ReadByte() / 255.0; + } + this.fade_start = ReadByte(); + this.fade_end = ReadByte(); + this.alpha_max = ReadByte(); + this.alpha_min = ReadByte(); + this.inactive = ReadByte(); + this.fade_vertical_offset = ReadShort(); + BGMScript_InitEntity(this); + } + + return = true; + + InterpolateOrigin_Note(this); + + this.saved = this.(fld); + + this.entremove = Ent_Wall_Remove; + this.draw = Ent_Wall_Draw; + if (isnew) IL_PUSH(g_drawables, this); + setpredraw(this, Ent_Wall_PreDraw); +} +#endif diff --git a/qcsrc/common/mapobjects/models.qh b/qcsrc/common/mapobjects/models.qh new file mode 100644 index 000000000..50170e251 --- /dev/null +++ b/qcsrc/common/mapobjects/models.qh @@ -0,0 +1,22 @@ +#pragma once + +#ifdef CSQC +entityclass(Wall); +classfield(Wall) .float lip; +classfield(Wall) .float bgmscriptangular; +classfield(Wall) .int lodmodelindex0, lodmodelindex1, lodmodelindex2; +classfield(Wall) .float loddistance1, loddistance2; +classfield(Wall) .vector saved; + +// Needed for interactive clientwalls +.float inactive; // Clientwall disappears when inactive +.float alpha_max, alpha_min; +// If fade_start > fade_end, fadeout will be inverted +// fade_vertical_offset is a vertival offset for player position +.float fade_start, fade_end, fade_vertical_offset; +.float default_solid; + +void Ent_Wall_Draw(entity this); + +void Ent_Wall_Remove(entity this); +#endif diff --git a/qcsrc/common/mapobjects/platforms.qc b/qcsrc/common/mapobjects/platforms.qc new file mode 100644 index 000000000..474787731 --- /dev/null +++ b/qcsrc/common/mapobjects/platforms.qc @@ -0,0 +1,227 @@ +#include "platforms.qh" +void generic_plat_blocked(entity this, entity blocker) +{ +#ifdef SVQC + if(this.dmg && blocker.takedamage != DAMAGE_NO) + { + if(this.dmgtime2 < time) + { + Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); + this.dmgtime2 = time + this.dmgtime; + } + + // Gib dead/dying stuff + if(IS_DEAD(blocker)) + Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); + } +#endif +} + +void plat_spawn_inside_trigger(entity this) +{ + entity trigger; + vector tmin, tmax; + + trigger = spawn(); + settouch(trigger, plat_center_touch); + set_movetype(trigger, MOVETYPE_NONE); + trigger.solid = SOLID_TRIGGER; + trigger.enemy = this; + + tmin = this.absmin + '25 25 0'; + tmax = this.absmax - '25 25 -8'; + tmin_z = tmax_z - (this.pos1_z - this.pos2_z + 8); + if (this.spawnflags & PLAT_LOW_TRIGGER) + tmax_z = tmin_z + 8; + + if (this.size_x <= 50) + { + tmin_x = (this.mins_x + this.maxs_x) / 2; + tmax_x = tmin_x + 1; + } + if (this.size_y <= 50) + { + tmin_y = (this.mins_y + this.maxs_y) / 2; + tmax_y = tmin_y + 1; + } + + if(tmin_x < tmax_x) + if(tmin_y < tmax_y) + if(tmin_z < tmax_z) + { + setsize (trigger, tmin, tmax); + return; + } + + // otherwise, something is fishy... + delete(trigger); + objerror(this, "plat_spawn_inside_trigger: platform has odd size or lip, can't spawn"); +} + +void plat_hit_top(entity this) +{ + _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM); + this.state = STATE_TOP; + + setthink(this, plat_go_down); + this.nextthink = this.ltime + 3; +} + +void plat_hit_bottom(entity this) +{ + _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM); + this.state = STATE_BOTTOM; +} + +void plat_go_down(entity this) +{ + _sound (this, CH_TRIGGER_SINGLE, this.noise, VOL_BASE, ATTEN_NORM); + this.state = STATE_DOWN; + SUB_CalcMove (this, this.pos2, TSPEED_LINEAR, this.speed, plat_hit_bottom); +} + +void plat_go_up(entity this) +{ + _sound (this, CH_TRIGGER_SINGLE, this.noise, VOL_BASE, ATTEN_NORM); + this.state = STATE_UP; + SUB_CalcMove (this, this.pos1, TSPEED_LINEAR, this.speed, plat_hit_top); +} + +void plat_center_touch(entity this, entity toucher) +{ +#ifdef SVQC + if (!toucher.iscreature) + return; + + if (toucher.health <= 0) + return; +#elif defined(CSQC) + if (!IS_PLAYER(toucher)) + return; + if(IS_DEAD(toucher)) + return; +#endif + + if (this.enemy.state == STATE_BOTTOM) { + plat_go_up(this.enemy); + } else if (this.enemy.state == STATE_TOP) + this.enemy.nextthink = this.enemy.ltime + 1; +} + +void plat_outside_touch(entity this, entity toucher) +{ +#ifdef SVQC + if (!toucher.iscreature) + return; + + if (toucher.health <= 0) + return; +#elif defined(CSQC) + if (!IS_PLAYER(toucher)) + return; +#endif + + if (this.enemy.state == STATE_TOP) { + entity e = this.enemy; + plat_go_down(e); + } +} + +void plat_trigger_use(entity this, entity actor, entity trigger) +{ + if (getthink(this)) + return; // already activated + plat_go_down(this); +} + + +void plat_crush(entity this, entity blocker) +{ + if((this.spawnflags & CRUSH) && (blocker.takedamage != DAMAGE_NO)) + { // KIll Kill Kill!! +#ifdef SVQC + Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); +#endif + } + else + { +#ifdef SVQC + if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) + { // Shall we bite? + Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); + // Gib dead/dying stuff + if(IS_DEAD(blocker)) + Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); + } +#endif + + if (this.state == STATE_UP) + plat_go_down (this); + else if (this.state == STATE_DOWN) + plat_go_up (this); + // when in other states, then the plat_crush event came delayed after + // plat state already had changed + // this isn't a bug per se! + } +} + +void plat_use(entity this, entity actor, entity trigger) +{ + this.use = func_null; + if (this.state != STATE_UP) + objerror (this, "plat_use: not in up state"); + plat_go_down(this); +} + +// WARNING: backwards compatibility because people don't use already existing fields :( +// TODO: Check if any maps use these fields and remove these fields if it doesn't break maps +.string sound1, sound2; + +void plat_reset(entity this) +{ + IFTARGETED + { + setorigin(this, this.pos1); + this.state = STATE_UP; + this.use = plat_use; + } + else + { + setorigin(this, this.pos2); + this.state = STATE_BOTTOM; + this.use = plat_trigger_use; + } + +#ifdef SVQC + this.SendFlags |= SF_TRIGGER_RESET; +#endif +} + +.float platmovetype_start_default, platmovetype_end_default; +bool set_platmovetype(entity e, string s) +{ + // sets platmovetype_start and platmovetype_end based on a string consisting of two values + + int n = tokenize_console(s); + if(n > 0) + e.platmovetype_start = stof(argv(0)); + else + e.platmovetype_start = 0; + + if(n > 1) + e.platmovetype_end = stof(argv(1)); + else + e.platmovetype_end = e.platmovetype_start; + + if(n > 2) + if(argv(2) == "force") + return true; // no checking, return immediately + + if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end)) + { + objerror(e, "Invalid platform move type; platform would go in reverse, which is not allowed."); + return false; + } + + return true; +} diff --git a/qcsrc/common/mapobjects/platforms.qh b/qcsrc/common/mapobjects/platforms.qh new file mode 100644 index 000000000..346cebc71 --- /dev/null +++ b/qcsrc/common/mapobjects/platforms.qh @@ -0,0 +1,15 @@ +#pragma once + + +const int PLAT_LOW_TRIGGER = BIT(0); + +.float dmgtime2; + +void plat_center_touch(entity this, entity toucher); +void plat_outside_touch(entity this, entity toucher); +void plat_trigger_use(entity this, entity actor, entity trigger); +void plat_go_up(entity this); +void plat_go_down(entity this); +void plat_crush(entity this, entity blocker); + +.float dmg; diff --git a/qcsrc/common/mapobjects/subs.qc b/qcsrc/common/mapobjects/subs.qc new file mode 100644 index 000000000..4877d0fb4 --- /dev/null +++ b/qcsrc/common/mapobjects/subs.qc @@ -0,0 +1,588 @@ +#include "subs.qh" +void SUB_NullThink(entity this) { } + +void SUB_CalcMoveDone(entity this); +void SUB_CalcAngleMoveDone(entity this); + +#ifdef SVQC +spawnfunc(info_null) +{ + delete(this); + // if anything breaks, tell the mapper to fix his map! info_null is meant to remove itself immediately. +} +#endif + +/* +================== +SUB_Friction + +Applies some friction to this +================== +*/ +.float friction; +void SUB_Friction (entity this) +{ + this.nextthink = time; + if(IS_ONGROUND(this)) + this.velocity = this.velocity * (1 - frametime * this.friction); +} + +/* +================== +SUB_VanishOrRemove + +Makes client invisible or removes non-client +================== +*/ +void SUB_VanishOrRemove (entity ent) +{ + if (IS_CLIENT(ent)) + { + // vanish + ent.alpha = -1; + ent.effects = 0; +#ifdef SVQC + ent.glow_size = 0; + ent.pflags = 0; +#endif + } + else + { + // remove + delete(ent); + } +} + +void SUB_SetFade_Think (entity this) +{ + if(this.alpha == 0) + this.alpha = 1; + setthink(this, SUB_SetFade_Think); + this.nextthink = time; + this.alpha -= frametime * this.fade_rate; + if (this.alpha < 0.01) + SUB_VanishOrRemove(this); + else + this.nextthink = time; +} + +/* +================== +SUB_SetFade + +Fade 'ent' out when time >= 'when' +================== +*/ +void SUB_SetFade (entity ent, float when, float fading_time) +{ + ent.fade_rate = 1/fading_time; + setthink(ent, SUB_SetFade_Think); + ent.nextthink = when; +} + +/* +============= +SUB_CalcMove + +calculate this.velocity and this.nextthink to reach dest from +this.origin traveling at speed +=============== +*/ +void SUB_CalcMoveDone(entity this) +{ + // After moving, set origin to exact final destination + + setorigin (this, this.finaldest); + this.velocity = '0 0 0'; + this.nextthink = -1; + if (this.think1 && this.think1 != SUB_CalcMoveDone) + this.think1 (this); +} + +.float platmovetype_turn; +void SUB_CalcMove_controller_think (entity this) +{ + float traveltime; + float phasepos; + float nexttick; + vector delta; + vector delta2; + vector veloc; + vector angloc; + vector nextpos; + delta = this.destvec; + delta2 = this.destvec2; + if(time < this.animstate_endtime) + { + nexttick = time + PHYS_INPUT_FRAMETIME; + + traveltime = this.animstate_endtime - this.animstate_starttime; + phasepos = (nexttick - this.animstate_starttime) / traveltime; // range: [0, 1] + phasepos = cubic_speedfunc(this.platmovetype_start, this.platmovetype_end, phasepos); + nextpos = this.origin + (delta * phasepos) + (delta2 * phasepos * phasepos); + // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning) + + if(this.owner.platmovetype_turn) + { + vector destangle; + destangle = delta + 2 * delta2 * phasepos; + destangle = vectoangles(destangle); + destangle_x = -destangle_x; // flip up / down orientation + + // take the shortest distance for the angles + vector v = this.owner.angles; + v.x -= 360 * floor((v.x - destangle_x) / 360 + 0.5); + v.y -= 360 * floor((v.y - destangle_y) / 360 + 0.5); + v.z -= 360 * floor((v.z - destangle_z) / 360 + 0.5); + this.owner.angles = v; + angloc = destangle - this.owner.angles; + angloc = angloc * (1 / PHYS_INPUT_FRAMETIME); // so it arrives for the next frame + this.owner.avelocity = angloc; + } + if(nexttick < this.animstate_endtime) + veloc = nextpos - this.owner.origin; + else + veloc = this.finaldest - this.owner.origin; + veloc = veloc * (1 / PHYS_INPUT_FRAMETIME); // so it arrives for the next frame + + this.owner.velocity = veloc; + this.nextthink = nexttick; + } + else + { + // derivative: delta + 2 * delta2 (e.g. for angle positioning) + entity own = this.owner; + setthink(own, this.think1); + // set the owner's reference to this entity to NULL + own.move_controller = NULL; + delete(this); + getthink(own)(own); + } +} + +void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector destin) +{ + // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + destin * t * t + // 2 * control * t - 2 * control * t * t + destin * t * t + // 2 * control * t + (destin - 2 * control) * t * t + + setorigin(controller, org); + control -= org; + destin -= org; + + controller.destvec = 2 * control; // control point + controller.destvec2 = destin - 2 * control; // quadratic part required to reach end point + // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (destin - control) +} + +void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector destin) +{ + // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + destin * t * t + // 2 * control * t - 2 * control * t * t + destin * t * t + // 2 * control * t + (destin - 2 * control) * t * t + + setorigin(controller, org); + destin -= org; + + controller.destvec = destin; // end point + controller.destvec2 = '0 0 0'; +} + +void SUB_CalcMove_Bezier (entity this, vector tcontrol, vector tdest, float tspeedtype, float tspeed, void(entity this) func) +{ + float traveltime; + entity controller; + + if (!tspeed) + objerror (this, "No speed is defined!"); + + this.think1 = func; + this.finaldest = tdest; + setthink(this, SUB_CalcMoveDone); + + switch(tspeedtype) + { + default: + case TSPEED_START: + traveltime = 2 * vlen(tcontrol - this.origin) / tspeed; + break; + case TSPEED_END: + traveltime = 2 * vlen(tcontrol - tdest) / tspeed; + break; + case TSPEED_LINEAR: + traveltime = vlen(tdest - this.origin) / tspeed; + break; + case TSPEED_TIME: + traveltime = tspeed; + break; + } + + if (traveltime < 0.1) // useless anim + { + this.velocity = '0 0 0'; + this.nextthink = this.ltime + 0.1; + return; + } + + // delete the previous controller, otherwise changing movement midway is glitchy + if (this.move_controller != NULL) + { + delete(this.move_controller); + } + controller = new(SUB_CalcMove_controller); + controller.owner = this; + this.move_controller = controller; + controller.platmovetype = this.platmovetype; + controller.platmovetype_start = this.platmovetype_start; + controller.platmovetype_end = this.platmovetype_end; + SUB_CalcMove_controller_setbezier(controller, this.origin, tcontrol, tdest); + controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit. + controller.animstate_starttime = time; + controller.animstate_endtime = time + traveltime; + setthink(controller, SUB_CalcMove_controller_think); + controller.think1 = getthink(this); + + // the thinking is now done by the controller + setthink(this, SUB_NullThink); // for PushMove + this.nextthink = this.ltime + traveltime; + + // invoke controller + getthink(controller)(controller); +} + +void SUB_CalcMove (entity this, vector tdest, float tspeedtype, float tspeed, void(entity this) func) +{ + vector delta; + float traveltime; + + if (!tspeed) + objerror (this, "No speed is defined!"); + + this.think1 = func; + this.finaldest = tdest; + setthink(this, SUB_CalcMoveDone); + + if (tdest == this.origin) + { + this.velocity = '0 0 0'; + this.nextthink = this.ltime + 0.1; + return; + } + + delta = tdest - this.origin; + + switch(tspeedtype) + { + default: + case TSPEED_START: + case TSPEED_END: + case TSPEED_LINEAR: + traveltime = vlen (delta) / tspeed; + break; + case TSPEED_TIME: + traveltime = tspeed; + break; + } + + // Very short animations don't really show off the effect + // of controlled animation, so let's just use linear movement. + // Alternatively entities can choose to specify non-controlled movement. + // The only currently implemented alternative movement is linear (value 1) + if (traveltime < 0.15 || (this.platmovetype_start == 1 && this.platmovetype_end == 1)) // is this correct? + { + this.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division + this.nextthink = this.ltime + traveltime; + return; + } + + // now just run like a bezier curve... + SUB_CalcMove_Bezier(this, (this.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func); +} + +void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void(entity this) func) +{ + SUB_CalcMove(ent, tdest, tspeedtype, tspeed, func); +} + +/* +============= +SUB_CalcAngleMove + +calculate this.avelocity and this.nextthink to reach destangle from +this.angles rotating + +The calling function should make sure this.setthink is valid +=============== +*/ +void SUB_CalcAngleMoveDone(entity this) +{ + // After rotating, set angle to exact final angle + this.angles = this.finalangle; + this.avelocity = '0 0 0'; + this.nextthink = -1; + if (this.think1 && this.think1 != SUB_CalcAngleMoveDone) // avoid endless loops + this.think1 (this); +} + +// FIXME: I fixed this function only for rotation around the main axes +void SUB_CalcAngleMove (entity this, vector destangle, float tspeedtype, float tspeed, void(entity this) func) +{ + if (!tspeed) + objerror (this, "No speed is defined!"); + + // take the shortest distance for the angles + this.angles_x -= 360 * floor((this.angles_x - destangle_x) / 360 + 0.5); + this.angles_y -= 360 * floor((this.angles_y - destangle_y) / 360 + 0.5); + this.angles_z -= 360 * floor((this.angles_z - destangle_z) / 360 + 0.5); + vector delta = destangle - this.angles; + float traveltime; + + switch(tspeedtype) + { + default: + case TSPEED_START: + case TSPEED_END: + case TSPEED_LINEAR: + traveltime = vlen (delta) / tspeed; + break; + case TSPEED_TIME: + traveltime = tspeed; + break; + } + + this.think1 = func; + this.finalangle = destangle; + setthink(this, SUB_CalcAngleMoveDone); + + if (traveltime < 0.1) + { + this.avelocity = '0 0 0'; + this.nextthink = this.ltime + 0.1; + return; + } + + this.avelocity = delta * (1 / traveltime); + this.nextthink = this.ltime + traveltime; +} + +void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void(entity this) func) +{ + SUB_CalcAngleMove (ent, destangle, tspeedtype, tspeed, func); +} + +#ifdef SVQC +void ApplyMinMaxScaleAngles(entity e) +{ + if(e.angles.x != 0 || e.angles.z != 0 || e.avelocity.x != 0 || e.avelocity.z != 0) // "weird" rotation + { + e.maxs = '1 1 1' * vlen( + '1 0 0' * max(-e.mins.x, e.maxs.x) + + '0 1 0' * max(-e.mins.y, e.maxs.y) + + '0 0 1' * max(-e.mins.z, e.maxs.z) + ); + e.mins = -e.maxs; + } + else if(e.angles.y != 0 || e.avelocity.y != 0) // yaw only is a bit better + { + e.maxs_x = vlen( + '1 0 0' * max(-e.mins.x, e.maxs.x) + + '0 1 0' * max(-e.mins.y, e.maxs.y) + ); + e.maxs_y = e.maxs.x; + e.mins_x = -e.maxs.x; + e.mins_y = -e.maxs.x; + } + if(e.scale) + setsize(e, e.mins * e.scale, e.maxs * e.scale); + else + setsize(e, e.mins, e.maxs); +} + +void SetBrushEntityModel(entity this) +{ + if(this.model != "") + { + precache_model(this.model); + if(this.mins != '0 0 0' || this.maxs != '0 0 0') + { + vector mi = this.mins; + vector ma = this.maxs; + _setmodel(this, this.model); // no precision needed + setsize(this, mi, ma); + } + else + _setmodel(this, this.model); // no precision needed + InitializeEntity(this, LODmodel_attach, INITPRIO_FINDTARGET); + } + setorigin(this, this.origin); + ApplyMinMaxScaleAngles(this); +} + +void SetBrushEntityModelNoLOD(entity this) +{ + if(this.model != "") + { + precache_model(this.model); + if(this.mins != '0 0 0' || this.maxs != '0 0 0') + { + vector mi = this.mins; + vector ma = this.maxs; + _setmodel(this, this.model); // no precision needed + setsize(this, mi, ma); + } + else + _setmodel(this, this.model); // no precision needed + } + setorigin(this, this.origin); + ApplyMinMaxScaleAngles(this); +} + +bool LOD_customize(entity this, entity client) +{ + if(autocvar_loddebug) + { + int d = autocvar_loddebug; + if(d == 1) + this.modelindex = this.lodmodelindex0; + else if(d == 2 || !this.lodmodelindex2) + this.modelindex = this.lodmodelindex1; + else // if(d == 3) + this.modelindex = this.lodmodelindex2; + return true; + } + + // TODO csqc network this so it only gets sent once + vector near_point = NearestPointOnBox(this, client.origin); + if(vdist(near_point - client.origin, <, this.loddistance1)) + this.modelindex = this.lodmodelindex0; + else if(!this.lodmodelindex2 || vdist(near_point - client.origin, <, this.loddistance2)) + this.modelindex = this.lodmodelindex1; + else + this.modelindex = this.lodmodelindex2; + + return true; +} + +void LOD_uncustomize(entity this) +{ + this.modelindex = this.lodmodelindex0; +} + +void LODmodel_attach(entity this) +{ + entity e; + + if(!this.loddistance1) + this.loddistance1 = 1000; + if(!this.loddistance2) + this.loddistance2 = 2000; + this.lodmodelindex0 = this.modelindex; + + if(this.lodtarget1 != "") + { + e = find(NULL, targetname, this.lodtarget1); + if(e) + { + this.lodmodel1 = e.model; + delete(e); + } + } + if(this.lodtarget2 != "") + { + e = find(NULL, targetname, this.lodtarget2); + if(e) + { + this.lodmodel2 = e.model; + delete(e); + } + } + + if(autocvar_loddebug < 0) + { + this.lodmodel1 = this.lodmodel2 = ""; // don't even initialize + } + + if(this.lodmodel1 != "") + { + vector mi, ma; + mi = this.mins; + ma = this.maxs; + + precache_model(this.lodmodel1); + _setmodel(this, this.lodmodel1); + this.lodmodelindex1 = this.modelindex; + + if(this.lodmodel2 != "") + { + precache_model(this.lodmodel2); + _setmodel(this, this.lodmodel2); + this.lodmodelindex2 = this.modelindex; + } + + this.modelindex = this.lodmodelindex0; + setsize(this, mi, ma); + } + + if(this.lodmodelindex1) + if (!getSendEntity(this)) + SetCustomizer(this, LOD_customize, LOD_uncustomize); +} + +/* +================ +InitTrigger +================ +*/ + +void SetMovedir(entity this) +{ + if(this.movedir != '0 0 0') + this.movedir = normalize(this.movedir); + else + { + makevectors(this.angles); + this.movedir = v_forward; + } + + this.angles = '0 0 0'; +} + +void InitTrigger(entity this) +{ +// trigger angles are used for one-way touches. An angle of 0 is assumed +// to mean no restrictions, so use a yaw of 360 instead. + SetMovedir(this); + this.solid = SOLID_TRIGGER; + SetBrushEntityModel(this); + set_movetype(this, MOVETYPE_NONE); + this.modelindex = 0; + this.model = ""; +} + +void InitSolidBSPTrigger(entity this) +{ +// trigger angles are used for one-way touches. An angle of 0 is assumed +// to mean no restrictions, so use a yaw of 360 instead. + SetMovedir(this); + this.solid = SOLID_BSP; + SetBrushEntityModel(this); + set_movetype(this, MOVETYPE_NONE); // why was this PUSH? -div0 +// this.modelindex = 0; + this.model = ""; +} + +bool InitMovingBrushTrigger(entity this) +{ +// trigger angles are used for one-way touches. An angle of 0 is assumed +// to mean no restrictions, so use a yaw of 360 instead. + this.solid = SOLID_BSP; + SetBrushEntityModel(this); + set_movetype(this, MOVETYPE_PUSH); + if(this.modelindex == 0) + { + objerror(this, "InitMovingBrushTrigger: no brushes found!"); + return false; + } + return true; +} +#endif diff --git a/qcsrc/common/mapobjects/subs.qh b/qcsrc/common/mapobjects/subs.qh new file mode 100644 index 000000000..0fa7db2f1 --- /dev/null +++ b/qcsrc/common/mapobjects/subs.qh @@ -0,0 +1,170 @@ +#pragma once +#include "defs.qh" + +.float friction; +void SUB_Friction(entity this); + +void SUB_NullThink(entity this); + +/* +================== +SUB_VanishOrRemove + +Makes client invisible or removes non-client +================== +*/ +void SUB_VanishOrRemove(entity ent); + +void SUB_SetFade_Think(entity this); + +/* +================== +SUB_SetFade + +Fade 'ent' out when time >= 'when' +================== +*/ +void SUB_SetFade(entity ent, float when, float fading_time); + +.vector finaldest, finalangle; //plat.qc stuff +.void(entity this) think1; +.float state; +.float t_length, t_width; + +.vector destvec; +.vector destvec2; + +.float delay; +.float wait; +.float lip; +.float speed; +.float sounds; +.string platmovetype; +.float platmovetype_start, platmovetype_end; + +//entity activator; + +.string killtarget; + +.vector pos1, pos2; +.vector mangle; + +.string target2; +.string target3; +.string target4; +.string curvetarget; +.float target_random; +.float trigger_reverse; + +// Keys player is holding +.float itemkeys; +// message delay for func_door locked by keys and key locks +// this field is used on player entities +.float key_door_messagetime; + +.vector dest1, dest2; + +.entity move_controller; + +const int TSPEED_TIME = -1; +const int TSPEED_LINEAR = 0; +const int TSPEED_START = 1; +const int TSPEED_END = 2; +// TODO average too? + +#ifdef CSQC +// this stuff is defined in the server side engine VM, so we must define it separately here +.float takedamage; +const int DAMAGE_NO = 0; +const int DAMAGE_YES = 1; +const int DAMAGE_AIM = 2; + +.string noise, noise1, noise2, noise3; // contains names of wavs to play + +.float max_health; // players maximum health is stored here +#endif + +#ifdef SVQC +spawnfunc(info_null); +#endif + +/* +============= +SUB_CalcMove + +calculate this.velocity and this.nextthink to reach dest from +this.origin traveling at speed +=============== +*/ +void SUB_CalcMoveDone(entity this); + +.float platmovetype_turn; +void SUB_CalcMove_controller_think (entity this); + +void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest); + +void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest); + +void SUB_CalcMove_Bezier (entity this, vector tcontrol, vector tdest, float tspeedtype, float tspeed, void(entity this) func); + +void SUB_CalcMove (entity this, vector tdest, float tspeedtype, float tspeed, void(entity this) func); + +void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void(entity this) func); + +#ifdef SVQC +void ApplyMinMaxScaleAngles(entity e); + +void SetBrushEntityModel(entity this); + +void SetBrushEntityModelNoLOD(entity this); + +int autocvar_loddebug; +.string lodtarget1; +.string lodtarget2; +.string lodmodel1; +.string lodmodel2; +.float lodmodelindex0; +.float lodmodelindex1; +.float lodmodelindex2; +.float loddistance1; +.float loddistance2; + +bool LOD_customize(entity this, entity client); + +void LOD_uncustomize(entity this); + +void LODmodel_attach(entity this); +#endif + +/* +============= +SUB_CalcAngleMove + +calculate this.avelocity and this.nextthink to reach destangle from +this.angles rotating + +The calling function should make sure this.think is valid +=============== +*/ +void SUB_CalcAngleMoveDone (entity this); + +// FIXME: I fixed this function only for rotation around the main axes +void SUB_CalcAngleMove (entity this, vector destangle, float tspeedtype, float tspeed, void(entity this) func); + +void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void(entity this) func); + +/* +================ +InitTrigger +================ +*/ + +#ifdef SVQC +void SetMovedir(entity this); + +void InitTrigger(entity this); + +void InitSolidBSPTrigger(entity this); + +bool InitMovingBrushTrigger(entity this); +#endif diff --git a/qcsrc/common/mapobjects/target/_mod.inc b/qcsrc/common/mapobjects/target/_mod.inc new file mode 100644 index 000000000..f9cc536ed --- /dev/null +++ b/qcsrc/common/mapobjects/target/_mod.inc @@ -0,0 +1,10 @@ +// generated file; do not modify +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/qcsrc/common/mapobjects/target/_mod.qh b/qcsrc/common/mapobjects/target/_mod.qh new file mode 100644 index 000000000..e3f4cede9 --- /dev/null +++ b/qcsrc/common/mapobjects/target/_mod.qh @@ -0,0 +1,10 @@ +// generated file; do not modify +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/qcsrc/common/mapobjects/target/changelevel.qc b/qcsrc/common/mapobjects/target/changelevel.qc new file mode 100644 index 000000000..114fd8718 --- /dev/null +++ b/qcsrc/common/mapobjects/target/changelevel.qc @@ -0,0 +1,55 @@ +#include "changelevel.qh" +#ifdef SVQC +.string chmap, gametype; +.entity chlevel_targ; + +void target_changelevel_use(entity this, entity actor, entity trigger) +{ + if(this.spawnflags & CHANGELEVEL_MULTIPLAYER) + { + // simply don't react if a non-player triggers it + if(!IS_PLAYER(actor)) { return; } + + actor.chlevel_targ = this; + + int plnum = 0; + int realplnum = 0; + // let's not count bots + FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { + ++realplnum; + if(it.chlevel_targ == this) + ++plnum; + }); + if(plnum < ceil(realplnum * min(1, this.count))) // 70% of players + return; + } + + if(this.gametype != "") + MapInfo_SwitchGameType(MapInfo_Type_FromString(this.gametype)); + + if (this.chmap == "") + localcmd("endmatch\n"); + else + localcmd(strcat("changelevel ", this.chmap, "\n")); +} + +/*target_changelevel +Target to change/end level +KEYS: +chmap: map to switch to, leave empty for endmatch +gametype: gametype for the next map +count: fraction of real players that need to trigger this entity for levelchange +SPAWNFLAGS: +CHANGELEVEL_MULTIPLAYER: multiplayer support +*/ + +spawnfunc(target_changelevel) +{ + this.use = target_changelevel_use; + + if(!this.count) + { + this.count = 0.7; + } +} +#endif diff --git a/qcsrc/common/mapobjects/target/changelevel.qh b/qcsrc/common/mapobjects/target/changelevel.qh new file mode 100644 index 000000000..f6e206edc --- /dev/null +++ b/qcsrc/common/mapobjects/target/changelevel.qh @@ -0,0 +1,4 @@ +#pragma once + + +const int CHANGELEVEL_MULTIPLAYER = BIT(1); diff --git a/qcsrc/common/mapobjects/target/kill.qc b/qcsrc/common/mapobjects/target/kill.qc new file mode 100644 index 000000000..2751c600e --- /dev/null +++ b/qcsrc/common/mapobjects/target/kill.qc @@ -0,0 +1,26 @@ +#include "kill.qh" +#include "location.qh" +#ifdef SVQC + +void target_kill_use(entity this, entity actor, entity trigger) +{ + if(actor.takedamage == DAMAGE_NO) + return; + + if(!actor.iscreature && !actor.damagedbytriggers) + return; + + Damage(actor, this, trigger, 1000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, actor.origin, '0 0 0'); +} + +spawnfunc(target_kill) +{ + this.classname = "target_kill"; + + if (this.message == "") + this.message = "was in the wrong place"; + + this.use = target_kill_use; +} + +#endif diff --git a/qcsrc/common/mapobjects/target/kill.qh b/qcsrc/common/mapobjects/target/kill.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/target/kill.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/target/levelwarp.qc b/qcsrc/common/mapobjects/target/levelwarp.qc new file mode 100644 index 000000000..21419cf81 --- /dev/null +++ b/qcsrc/common/mapobjects/target/levelwarp.qc @@ -0,0 +1,21 @@ +#include "levelwarp.qh" + +#ifdef SVQC +void target_levelwarp_use(entity this, entity actor, entity trigger) +{ + if(!autocvar_g_campaign) + return; // only in campaign + + if(this.cnt) + CampaignLevelWarp(this.cnt - 1); // specific level + else + CampaignLevelWarp(-1); // next level +} + +spawnfunc(target_levelwarp) +{ + // this.cnt is index (starting from 1) of the campaign level to warp to + // 0 means next level + this.use = target_levelwarp_use; +} +#endif diff --git a/qcsrc/common/mapobjects/target/levelwarp.qh b/qcsrc/common/mapobjects/target/levelwarp.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/target/levelwarp.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/target/location.qc b/qcsrc/common/mapobjects/target/location.qc new file mode 100644 index 000000000..5774f45f9 --- /dev/null +++ b/qcsrc/common/mapobjects/target/location.qc @@ -0,0 +1,25 @@ +#include "location.qh" +#ifdef SVQC +void target_push_init(entity this); + +spawnfunc(target_location) +{ + this.classname = "target_location"; + // location name in netname + // eventually support: count, teamgame selectors, line of sight? + + target_push_init(this); + + IL_PUSH(g_locations, this); +} + +spawnfunc(info_location) +{ + this.classname = "target_location"; + this.message = this.netname; + + target_push_init(this); + + IL_PUSH(g_locations, this); +} +#endif diff --git a/qcsrc/common/mapobjects/target/location.qh b/qcsrc/common/mapobjects/target/location.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/target/location.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/target/music.qc b/qcsrc/common/mapobjects/target/music.qc new file mode 100644 index 000000000..5a63872db --- /dev/null +++ b/qcsrc/common/mapobjects/target/music.qc @@ -0,0 +1,344 @@ +#include "music.qh" +#if defined(CSQC) +#elif defined(MENUQC) +#elif defined(SVQC) + #include + #include + #include + #include +#endif + +REGISTER_NET_TEMP(TE_CSQC_TARGET_MUSIC) +REGISTER_NET_LINKED(ENT_CLIENT_TRIGGER_MUSIC) + +#ifdef SVQC + +IntrusiveList g_targetmusic_list; +STATIC_INIT(g_targetmusic_list) +{ + g_targetmusic_list = IL_NEW(); +} + +// values: +// volume +// noise +// targetname +// lifetime +// fade_time +// fade_rate +// when triggered, the music is overridden for activator until lifetime (or forever, if lifetime is 0) +// when targetname is not set, THIS ONE is default +void target_music_sendto(entity this, int to, bool is) +{ + WriteHeader(to, TE_CSQC_TARGET_MUSIC); + WriteShort(to, etof(this)); + WriteByte(to, this.volume * 255.0 * is); + WriteByte(to, this.fade_time * 16.0); + WriteByte(to, this.fade_rate * 16.0); + WriteByte(to, this.lifetime); + WriteString(to, this.noise); +} +void target_music_reset(entity this) +{ + if (this.targetname == "") + { + target_music_sendto(this, MSG_ALL, true); + } +} +void target_music_kill() +{ + IL_EACH(g_targetmusic_list, true, + { + it.volume = 0; + if (it.targetname == "") + target_music_sendto(it, MSG_ALL, true); + else + target_music_sendto(it, MSG_ALL, false); + }); +} +void target_music_use(entity this, entity actor, entity trigger) +{ + if(!actor) + return; + if(IS_REAL_CLIENT(actor)) + { + msg_entity = actor; + target_music_sendto(this, MSG_ONE, true); + } + FOREACH_CLIENT(IS_SPEC(it) && it.enemy == actor, { + msg_entity = it; + target_music_sendto(this, MSG_ONE, true); + }); +} +spawnfunc(target_music) +{ + this.use = target_music_use; + this.reset = target_music_reset; + if(!this.volume) + this.volume = 1; + IL_PUSH(g_targetmusic_list, this); + if(this.targetname == "") + target_music_sendto(this, MSG_INIT, true); + else + target_music_sendto(this, MSG_INIT, false); +} +void TargetMusic_RestoreGame() +{ + IL_EACH(g_targetmusic_list, true, + { + if(it.targetname == "") + target_music_sendto(it, MSG_INIT, true); + else + target_music_sendto(it, MSG_INIT, false); + }); +} +// values: +// volume +// noise +// targetname +// fade_time +// spawnflags: +// START_DISABLED +// can be disabled/enabled for everyone with relays +bool trigger_music_SendEntity(entity this, entity to, int sendflags) +{ + WriteHeader(MSG_ENTITY, ENT_CLIENT_TRIGGER_MUSIC); + WriteByte(MSG_ENTITY, sendflags); + if(sendflags & SF_MUSIC_ORIGIN) + { + WriteVector(MSG_ENTITY, this.origin); + } + if(sendflags & SF_TRIGGER_INIT) + { + if(this.model != "null") + { + WriteShort(MSG_ENTITY, this.modelindex); + WriteVector(MSG_ENTITY, this.mins); + WriteVector(MSG_ENTITY, this.maxs); + } + else + { + WriteShort(MSG_ENTITY, 0); + WriteVector(MSG_ENTITY, this.maxs); + } + WriteByte(MSG_ENTITY, this.volume * 255.0); + WriteByte(MSG_ENTITY, this.fade_time * 16.0); + WriteByte(MSG_ENTITY, this.fade_rate * 16.0); + WriteString(MSG_ENTITY, this.noise); + } + if(sendflags & SF_TRIGGER_UPDATE) + { + WriteByte(MSG_ENTITY, this.active); + } + return true; +} +void trigger_music_reset(entity this) +{ + if(this.spawnflags & START_DISABLED) + { + this.setactive(this, ACTIVE_NOT); + } + else + { + this.setactive(this, ACTIVE_ACTIVE); + } +} + +spawnfunc(trigger_music) +{ + if(this.model != "") + { + _setmodel(this, this.model); + } + if(!this.volume) + { + this.volume = 1; + } + if(!this.modelindex) + { + setorigin(this, this.origin + this.mins); + setsize(this, '0 0 0', this.maxs - this.mins); + } + + this.setactive = generic_netlinked_setactive; + this.use = generic_netlinked_legacy_use; // backwards compatibility + this.reset = trigger_music_reset; + this.reset(this); + + Net_LinkEntity(this, false, 0, trigger_music_SendEntity); +} +#elif defined(CSQC) + +entity TargetMusic_list; +STATIC_INIT(TargetMusic_list) +{ + TargetMusic_list = LL_NEW(); +} + +void TargetMusic_Advance() +{ + // run AFTER all the thinks! + entity best = music_default; + if (music_target && time < music_target.lifetime) + { + best = music_target; + } + if (music_trigger) + { + best = music_trigger; + } + LL_EACH(TargetMusic_list, it.noise, { + const float vol0 = (getsoundtime(it, CH_BGM_SINGLE) >= 0) ? it.lastvol : -1; + if (it == best) + { + // increase volume + it.state = (it.fade_time > 0) ? bound(0, it.state + frametime / it.fade_time, 1) : 1; + } + else + { + // decrease volume + it.state = (it.fade_rate > 0) ? bound(0, it.state - frametime / it.fade_rate, 1) : 0; + } + const float vol = it.state * it.volume * autocvar_bgmvolume; + if (vol != vol0) + { + if(vol0 < 0) + sound7(it, CH_BGM_SINGLE, it.noise, vol, ATTEN_NONE, 0, BIT(4)); // restart + else + sound7(it, CH_BGM_SINGLE, "", vol, ATTEN_NONE, 0, BIT(4)); + it.lastvol = vol; + } + }); + music_trigger = NULL; + bgmtime = (best) ? getsoundtime(best, CH_BGM_SINGLE) : gettime(GETTIME_CDTRACK); +} + +NET_HANDLE(TE_CSQC_TARGET_MUSIC, bool isNew) +{ + Net_TargetMusic(); + return true; +} + +void Net_TargetMusic() +{ + const int id = ReadShort(); + const float vol = ReadByte() / 255.0; + const float fai = ReadByte() / 16.0; + const float fao = ReadByte() / 16.0; + const float tim = ReadByte(); + const string noi = ReadString(); + + entity e = NULL; + LL_EACH(TargetMusic_list, it.count == id, { e = it; break; }); + if (!e) + { + LL_PUSH(TargetMusic_list, e = new_pure(TargetMusic)); + e.count = id; + } + if(e.noise != noi) + { + strcpy(e.noise, noi); + precache_sound(e.noise); + _sound(e, CH_BGM_SINGLE, e.noise, 0, ATTEN_NONE); + if(getsoundtime(e, CH_BGM_SINGLE) < 0) + { + LOG_TRACEF("Cannot initialize sound %s", e.noise); + strfree(e.noise); + } + } + e.volume = vol; + e.fade_time = fai; + e.fade_rate = fao; + if(vol > 0) + { + if(tim == 0) + { + music_default = e; + if(!music_disabled) + { + e.state = 2; + cvar_settemp("music_playlist_index", "-1"); // don't use playlists + localcmd("cd stop\n"); // just in case + music_disabled = 1; + } + } + else + { + music_target = e; + e.lifetime = time + tim; + } + } +} + +void Ent_TriggerMusic_Think(entity this) +{ + if(this.active == ACTIVE_NOT) + { + return; + } + vector org = (csqcplayer) ? csqcplayer.origin : view_origin; + if(WarpZoneLib_BoxTouchesBrush(org + STAT(PL_MIN), org + STAT(PL_MAX), this, NULL)) + { + music_trigger = this; + } +} + +void Ent_TriggerMusic_Remove(entity this) +{ + strfree(this.noise); +} + +NET_HANDLE(ENT_CLIENT_TRIGGER_MUSIC, bool isnew) +{ + int sendflags = ReadByte(); + if(sendflags & SF_MUSIC_ORIGIN) + { + this.origin = ReadVector(); + } + if(sendflags & SF_TRIGGER_INIT) + { + this.modelindex = ReadShort(); + if(this.modelindex) + { + this.mins = ReadVector(); + this.maxs = ReadVector(); + } + else + { + this.mins = '0 0 0'; + this.maxs = ReadVector(); + } + + this.volume = ReadByte() / 255.0; + this.fade_time = ReadByte() / 16.0; + this.fade_rate = ReadByte() / 16.0; + string s = this.noise; + strcpy(this.noise, ReadString()); + if(this.noise != s) + { + precache_sound(this.noise); + sound7(this, CH_BGM_SINGLE, this.noise, 0, ATTEN_NONE, 0, BIT(4)); + if(getsoundtime(this, CH_BGM_SINGLE) < 0) + { + LOG_WARNF("Cannot initialize sound %s", this.noise); + strfree(this.noise); + } + } + } + if(sendflags & SF_TRIGGER_UPDATE) + { + this.active = ReadByte(); + } + + setorigin(this, this.origin); + setsize(this, this.mins, this.maxs); + this.draw = Ent_TriggerMusic_Think; + if(isnew) + { + LL_PUSH(TargetMusic_list, this); + IL_PUSH(g_drawables, this); + } + return true; +} + +#endif diff --git a/qcsrc/common/mapobjects/target/music.qh b/qcsrc/common/mapobjects/target/music.qh new file mode 100644 index 000000000..ccf3f674e --- /dev/null +++ b/qcsrc/common/mapobjects/target/music.qh @@ -0,0 +1,28 @@ +#pragma once + +.float lifetime; + +const int SF_MUSIC_ORIGIN = BIT(2); + +#ifdef CSQC +float music_disabled; +entity music_default; +entity music_target; +entity music_trigger; +// FIXME also control bgmvolume here, to not require a target_music for the default track. + +entityclass(TargetMusic); +classfield(TargetMusic) .int state; +classfield(TargetMusic) .float lastvol; + +void TargetMusic_Advance(); + +void Net_TargetMusic(); + +void Ent_TriggerMusic_Think(entity this); + +void Ent_TriggerMusic_Remove(entity this); + +#elif defined(SVQC) +void target_music_kill(); +#endif diff --git a/qcsrc/common/mapobjects/target/spawn.qc b/qcsrc/common/mapobjects/target/spawn.qc new file mode 100644 index 000000000..9c999ed4d --- /dev/null +++ b/qcsrc/common/mapobjects/target/spawn.qc @@ -0,0 +1,340 @@ +#include "spawn.qh" +#if defined(CSQC) +#elif defined(MENUQC) +#elif defined(SVQC) + #include + #include +#endif + +#ifdef SVQC + +// spawner entity +// "classname" "target_spawn" +// "message" "fieldname value fieldname value ..." +// "spawnflags" +// ON_MAPLOAD = trigger on map load + +float target_spawn_initialized; +.void(entity this) target_spawn_spawnfunc; +float target_spawn_spawnfunc_field; +.entity target_spawn_activator; +.float target_spawn_id; +float target_spawn_count; + +void target_spawn_helper_setmodel(entity this) +{ + _setmodel(this, this.model); +} + +void target_spawn_helper_setsize(entity this) +{ + setsize(this, this.mins, this.maxs); +} + +void target_spawn_edit_entity(entity this, entity e, string msg, entity kt, entity t2, entity t3, entity t4, entity act, entity trigger) +{ + float i, n, valuefieldpos; + string key, value, valuefield, valueoffset, valueoffsetrandom; + entity valueent; + vector data, data2; + + n = tokenize_console(msg); + + for(i = 0; i < n-1; i += 2) + { + key = argv(i); + value = argv(i+1); + if(key == "$") + { + data.x = -1; + data.y = FIELD_STRING; + } + else + { + data = stov(db_get(TemporaryDB, strcat("/target_spawn/field/", key))); + if(data.y == 0) // undefined field, i.e., invalid type + { + LOG_INFO("target_spawn: invalid/unknown entity key ", key, " specified, ignored!"); + continue; + } + } + if(substring(value, 0, 1) == "$") + { + value = substring(value, 1, strlen(value) - 1); + if(substring(value, 0, 1) == "$") + { + // deferred replacement + // do nothing + // useful for creating target_spawns with this! + } + else + { + // replace me! + valuefieldpos = strstrofs(value, "+", 0); + valueoffset = ""; + if(valuefieldpos != -1) + { + valueoffset = substring(value, valuefieldpos + 1, strlen(value) - valuefieldpos - 1); + value = substring(value, 0, valuefieldpos); + } + + valuefieldpos = strstrofs(valueoffset, "+", 0); + valueoffsetrandom = ""; + if(valuefieldpos != -1) + { + valueoffsetrandom = substring(valueoffset, valuefieldpos + 1, strlen(valueoffset) - valuefieldpos - 1); + valueoffset = substring(valueoffset, 0, valuefieldpos); + } + + valuefieldpos = strstrofs(value, ".", 0); + valuefield = ""; + if(valuefieldpos != -1) + { + valuefield = substring(value, valuefieldpos + 1, strlen(value) - valuefieldpos - 1); + value = substring(value, 0, valuefieldpos); + } + + if(value == "self") + { + valueent = this; + value = ""; + } + else if(value == "activator") + { + valueent = act; + value = ""; + } + else if(value == "other") + { + valueent = trigger; + value = ""; + } + else if(value == "pusher") + { + if(time < act.pushltime) + valueent = act.pusher; + else + valueent = NULL; + value = ""; + } + else if(value == "target") + { + valueent = e; + value = ""; + } + else if(value == "killtarget") + { + valueent = kt; + value = ""; + } + else if(value == "target2") + { + valueent = t2; + value = ""; + } + else if(value == "target3") + { + valueent = t3; + value = ""; + } + else if(value == "target4") + { + valueent = t4; + value = ""; + } + else if(value == "time") + { + valueent = NULL; + value = ftos(time); + } + else + { + LOG_INFO("target_spawn: invalid/unknown variable replacement ", value, " specified, ignored!"); + continue; + } + + if(valuefield == "") + { + if(value == "") + value = ftos(etof(valueent)); + } + else + { + if(value != "") + { + LOG_INFO("target_spawn: try to get a field of a non-entity, ignored!"); + continue; + } + data2 = stov(db_get(TemporaryDB, strcat("/target_spawn/field/", valuefield))); + if(data2_y == 0) // undefined field, i.e., invalid type + { + LOG_INFO("target_spawn: invalid/unknown entity key replacement ", valuefield, " specified, ignored!"); + continue; + } + value = getentityfieldstring(data2_x, valueent); + } + + if(valueoffset != "") + { + switch(data.y) + { + case FIELD_STRING: + value = strcat(value, valueoffset); + break; + case FIELD_FLOAT: + value = ftos(stof(value) + stof(valueoffset)); + break; + case FIELD_VECTOR: + value = vtos(stov(value) + stov(valueoffset)); + break; + default: + LOG_INFO("target_spawn: only string, float and vector fields can do calculations, calculation ignored!"); + break; + } + } + + if(valueoffsetrandom != "") + { + switch(data.y) + { + case FIELD_FLOAT: + value = ftos(stof(value) + random() * stof(valueoffsetrandom)); + break; + case FIELD_VECTOR: + data2 = stov(valueoffsetrandom); + value = vtos(stov(value) + random() * data2_x * '1 0 0' + random() * data2_y * '0 1 0' + random() * data2_z * '0 0 1'); + break; + default: + LOG_INFO("target_spawn: only float and vector fields can do random calculations, calculation ignored!"); + break; + } + } + } + } + if(key == "$") + { + if(substring(value, 0, 1) == "_") + value = strcat("target_spawn_helper", value); + putentityfieldstring(target_spawn_spawnfunc_field, e, value); + + e.target_spawn_spawnfunc(e); + + // We called an external function, so we have to re-tokenize msg. + n = tokenize_console(msg); + } + else + { + if(data.y == FIELD_VECTOR) + value = strreplace("'", "", value); // why?!? + putentityfieldstring(data.x, e, value); + } + } +} + +void target_spawn_useon(entity e, entity this, entity actor, entity trigger) +{ + this.target_spawn_activator = actor; + target_spawn_edit_entity( + this, + e, + this.message, + find(NULL, targetname, this.killtarget), + find(NULL, targetname, this.target2), + find(NULL, targetname, this.target3), + find(NULL, targetname, this.target4), + actor, + trigger + ); +} + +bool target_spawn_cancreate(entity this) +{ + float c; + entity e; + + c = this.count; + if(c == 0) // no limit? + return true; + + ++c; // increase count to not include MYSELF + for(e = NULL; (e = findfloat(e, target_spawn_id, this.target_spawn_id)); --c) + ; + + // if c now is 0, we have AT LEAST the given count (maybe more), so don't spawn any more + if(c == 0) + return false; + return true; +} + +void target_spawn_use(entity this, entity actor, entity trigger) +{ + if(this.target == "") + { + // spawn new entity + if(!target_spawn_cancreate(this)) + return; + entity e = spawn(); + e.spawnfunc_checked = true; + target_spawn_useon(e, this, actor, trigger); + e.target_spawn_id = this.target_spawn_id; + } + else if(this.target == "*activator") + { + // edit entity + if(actor) + target_spawn_useon(actor, this, actor, trigger); + } + else + { + // edit entity + FOREACH_ENTITY_STRING(targetname, this.target, + { + target_spawn_useon(it, this, actor, trigger); + }); + } +} + +void target_spawn_spawnfirst(entity this) +{ + entity act = this.target_spawn_activator; + if(this.spawnflags & ON_MAPLOAD) + target_spawn_use(this, act, NULL); +} + +void initialize_field_db() +{ + if(!target_spawn_initialized) + { + float n, i; + string fn; + vector prev, next; + float ft; + + n = numentityfields(); + for(i = 0; i < n; ++i) + { + fn = entityfieldname(i); + ft = entityfieldtype(i); + next = i * '1 0 0' + ft * '0 1 0' + '0 0 1'; + prev = stov(db_get(TemporaryDB, strcat("/target_spawn/field/", fn))); + if(prev.y == 0) + { + db_put(TemporaryDB, strcat("/target_spawn/field/", fn), vtos(next)); + if(fn == "target_spawn_spawnfunc") + target_spawn_spawnfunc_field = i; + } + } + + target_spawn_initialized = 1; + } +} + +spawnfunc(target_spawn) +{ + initialize_field_db(); + this.use = target_spawn_use; + this.message = strzone(strreplace("'", "\"", this.message)); + this.target_spawn_id = ++target_spawn_count; + InitializeEntity(this, target_spawn_spawnfirst, INITPRIO_LAST); +} +#endif diff --git a/qcsrc/common/mapobjects/target/spawn.qh b/qcsrc/common/mapobjects/target/spawn.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/target/spawn.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/target/spawnpoint.qc b/qcsrc/common/mapobjects/target/spawnpoint.qc new file mode 100644 index 000000000..fe1538551 --- /dev/null +++ b/qcsrc/common/mapobjects/target/spawnpoint.qc @@ -0,0 +1,24 @@ +#include "spawnpoint.qh" + +#ifdef SVQC +void target_spawnpoint_use(entity this, entity actor, entity trigger) +{ + if(this.active != ACTIVE_ACTIVE) + return; + + actor.spawnpoint_targ = this; +} + +void target_spawnpoint_reset(entity this) +{ + this.active = ACTIVE_ACTIVE; +} + +// TODO: persistent spawnflag? +spawnfunc(target_spawnpoint) +{ + this.active = ACTIVE_ACTIVE; + this.use = target_spawnpoint_use; + this.reset = target_spawnpoint_reset; +} +#endif diff --git a/qcsrc/common/mapobjects/target/spawnpoint.qh b/qcsrc/common/mapobjects/target/spawnpoint.qh new file mode 100644 index 000000000..2eeb8da62 --- /dev/null +++ b/qcsrc/common/mapobjects/target/spawnpoint.qh @@ -0,0 +1,5 @@ +#pragma once + +#ifdef SVQC +.entity spawnpoint_targ; +#endif diff --git a/qcsrc/common/mapobjects/target/speaker.qc b/qcsrc/common/mapobjects/target/speaker.qc new file mode 100644 index 000000000..11c9ad7ba --- /dev/null +++ b/qcsrc/common/mapobjects/target/speaker.qc @@ -0,0 +1,138 @@ +#include "speaker.qh" +#ifdef SVQC +// TODO add a way to do looped sounds with sound(); then complete this entity +void target_speaker_use_off(entity this, entity actor, entity trigger); +void target_speaker_use_activator(entity this, entity actor, entity trigger) +{ + if (!IS_REAL_CLIENT(actor)) + return; + string snd; + if(substring(this.noise, 0, 1) == "*") + { + var .string sample = GetVoiceMessageSampleField(substring(this.noise, 1, -1)); + if(GetPlayerSoundSampleField_notFound) + snd = SND(Null); + else if(actor.(sample) == "") + snd = SND(Null); + else + { + tokenize_console(actor.(sample)); + float n; + n = stof(argv(1)); + if(n > 0) + snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization + else + snd = strcat(argv(0), ".wav"); // randomization + } + } + else + snd = this.noise; + msg_entity = actor; + soundto(MSG_ONE, this, CH_TRIGGER, snd, VOL_BASE * this.volume, this.atten); +} +void target_speaker_use_on(entity this, entity actor, entity trigger) +{ + string snd; + if(substring(this.noise, 0, 1) == "*") + { + var .string sample = GetVoiceMessageSampleField(substring(this.noise, 1, -1)); + if(GetPlayerSoundSampleField_notFound) + snd = SND(Null); + else if(actor.(sample) == "") + snd = SND(Null); + else + { + tokenize_console(actor.(sample)); + float n; + n = stof(argv(1)); + if(n > 0) + snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization + else + snd = strcat(argv(0), ".wav"); // randomization + } + } + else + snd = this.noise; + _sound(this, CH_TRIGGER_SINGLE, snd, VOL_BASE * this.volume, this.atten); + if(this.spawnflags & (SPEAKER_LOOPED_ON + SPEAKER_LOOPED_OFF)) + this.use = target_speaker_use_off; +} +void target_speaker_use_off(entity this, entity actor, entity trigger) +{ + sound(this, CH_TRIGGER_SINGLE, SND_Null, VOL_BASE * this.volume, this.atten); + this.use = target_speaker_use_on; +} +void target_speaker_reset(entity this) +{ + if(this.spawnflags & SPEAKER_LOOPED_ON) + { + if(this.use == target_speaker_use_on) + target_speaker_use_on(this, NULL, NULL); + } + else if(this.spawnflags & SPEAKER_LOOPED_OFF) + { + if(this.use == target_speaker_use_off) + target_speaker_use_off(this, NULL, NULL); + } +} + +spawnfunc(target_speaker) +{ + // TODO: "*" prefix to sound file name + // TODO: wait and random (just, HOW? random is not a field) + if(this.noise) + precache_sound (this.noise); + + if(!this.atten && (this.spawnflags & SPEAKER_GLOBAL)) + { + LOG_WARN("target_speaker uses legacy spawnflag GLOBAL (BIT(2)), please set atten to -1 instead"); + this.atten = -1; + } + + if(!this.atten) + { + IFTARGETED + this.atten = ATTEN_NORM; + else + this.atten = ATTEN_STATIC; + } + else if(this.atten < 0) + this.atten = 0; + + if(!this.volume) + this.volume = 1; + + IFTARGETED + { + if(this.spawnflags & SPEAKER_ACTIVATOR) + this.use = target_speaker_use_activator; + else if(this.spawnflags & SPEAKER_LOOPED_ON) + { + target_speaker_use_on(this, NULL, NULL); + this.reset = target_speaker_reset; + } + else if(this.spawnflags & SPEAKER_LOOPED_OFF) + { + this.use = target_speaker_use_on; + this.reset = target_speaker_reset; + } + else + this.use = target_speaker_use_on; + } + else if(this.spawnflags & SPEAKER_LOOPED_ON) + { + ambientsound (this.origin, this.noise, VOL_BASE * this.volume, this.atten); + delete(this); + } + else if(this.spawnflags & SPEAKER_LOOPED_OFF) + { + objerror(this, "This sound entity can never be activated"); + } + else + { + // Quake/Nexuiz fallback + ambientsound (this.origin, this.noise, VOL_BASE * this.volume, this.atten); + delete(this); + } +} +#endif diff --git a/qcsrc/common/mapobjects/target/speaker.qh b/qcsrc/common/mapobjects/target/speaker.qh new file mode 100644 index 000000000..53e0194be --- /dev/null +++ b/qcsrc/common/mapobjects/target/speaker.qh @@ -0,0 +1,7 @@ +#pragma once + + +const int SPEAKER_LOOPED_ON = BIT(0); +const int SPEAKER_LOOPED_OFF = BIT(1); +const int SPEAKER_GLOBAL = BIT(2); // legacy, set speaker atten to -1 instead +const int SPEAKER_ACTIVATOR = BIT(3); diff --git a/qcsrc/common/mapobjects/target/voicescript.qc b/qcsrc/common/mapobjects/target/voicescript.qc new file mode 100644 index 000000000..6dfb568a8 --- /dev/null +++ b/qcsrc/common/mapobjects/target/voicescript.qc @@ -0,0 +1,102 @@ +#include "voicescript.qh" +#ifdef SVQC +.entity voicescript; // attached voice script +.float voicescript_index; // index of next voice, or -1 to use the randomized ones +.float voicescript_nextthink; // time to play next voice +.float voicescript_voiceend; // time when this voice ends + +void target_voicescript_clear(entity pl) +{ + pl.voicescript = NULL; +} + +void target_voicescript_use(entity this, entity actor, entity trigger) +{ + if(actor.voicescript != this) + { + actor.voicescript = this; + actor.voicescript_index = 0; + actor.voicescript_nextthink = time + this.delay; + } +} + +void target_voicescript_next(entity pl) +{ + entity vs; + float i, n, dt; + + vs = pl.voicescript; + if(!vs) + return; + if(vs.message == "") + return; + if (!IS_PLAYER(pl)) + return; + if(game_stopped) + return; + + if(time >= pl.voicescript_voiceend) + { + if(time >= pl.voicescript_nextthink) + { + // get the next voice... + n = tokenize_console(vs.message); + + if(pl.voicescript_index < vs.cnt) + i = pl.voicescript_index * 2; + else if(n > vs.cnt * 2) + i = ((pl.voicescript_index - vs.cnt) % ((n - vs.cnt * 2 - 1) / 2)) * 2 + vs.cnt * 2 + 1; + else + i = -1; + + if(i >= 0) + { + play2(pl, strcat(vs.netname, "/", argv(i), ".wav")); + dt = stof(argv(i + 1)); + if(dt >= 0) + { + pl.voicescript_voiceend = time + dt; + pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random()); + } + else + { + pl.voicescript_voiceend = time - dt; + pl.voicescript_nextthink = pl.voicescript_voiceend; + } + + pl.voicescript_index += 1; + } + else + { + pl.voicescript = NULL; // stop trying then + } + } + } +} + +spawnfunc(target_voicescript) +{ + // netname: directory of the sound files + // message: list of "sound file" duration "sound file" duration, a *, and again a list + // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7 + // Here, a - in front of the duration means that no delay is to be + // added after this message + // wait: average time between messages + // delay: initial delay before the first message + + float i, n; + this.use = target_voicescript_use; + + n = tokenize_console(this.message); + this.cnt = n / 2; + for(i = 0; i+1 < n; i += 2) + { + if(argv(i) == "*") + { + this.cnt = i / 2; + ++i; + } + precache_sound(strcat(this.netname, "/", argv(i), ".wav")); + } +} +#endif diff --git a/qcsrc/common/mapobjects/target/voicescript.qh b/qcsrc/common/mapobjects/target/voicescript.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/target/voicescript.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/teleporters.qc b/qcsrc/common/mapobjects/teleporters.qc new file mode 100644 index 000000000..ec6a26d18 --- /dev/null +++ b/qcsrc/common/mapobjects/teleporters.qc @@ -0,0 +1,315 @@ +#include "teleporters.qh" + +#if defined(CSQC) +#elif defined(MENUQC) +#elif defined(SVQC) + #include + #include + #include + #include "../constants.qh" + #include "../mapobjects/subs.qh" + #include "../util.qh" + #include + #include + #include + #include + #include "../deathtypes/all.qh" + #include "../turrets/sv_turrets.qh" + #include "../vehicles/all.qh" + #include "../mapinfo.qh" + #include +#endif + +#ifdef SVQC +float check_tdeath(entity player, vector org, vector telefragmin, vector telefragmax) +{ + if (IS_PLAYER(player) && !IS_DEAD(player)) + { + TDEATHLOOP(org) + { + #ifdef SVQC + if (!(teamplay && autocvar_g_telefrags_teamplay && head.team == player.team)) + #endif + if(IS_PLAYER(head)) + if(!IS_DEAD(head)) + return 1; + } + } + return 0; +} + +void trigger_teleport_link(entity this); + +void tdeath(entity player, entity teleporter, entity telefragger, vector telefragmin, vector telefragmax) +{ + TDEATHLOOP(player.origin) + { + if (IS_PLAYER(player) && player.health >= 1) + { + if (!(teamplay && autocvar_g_telefrags_teamplay && head.team == player.team)) + { + if(IS_PLAYER(head)) + if(head.health >= 1) + ++tdeath_hit; + Damage (head, teleporter, telefragger, 10000, DEATH_TELEFRAG.m_id, DMG_NOWEP, head.origin, '0 0 0'); + } + } + else // dead bodies and monsters gib themselves instead of telefragging + Damage (telefragger, teleporter, telefragger, 10000, DEATH_TELEFRAG.m_id, DMG_NOWEP, telefragger.origin, '0 0 0'); + } +} + +void spawn_tdeath(vector v0, entity e, vector v) +{ + tdeath(e, e, e, '0 0 0', '0 0 0'); +} +#endif + +void TeleportPlayer(entity teleporter, entity player, vector to, vector to_angles, vector to_velocity, vector telefragmin, vector telefragmax, float tflags) +{ + entity telefragger; + vector from; + + if(teleporter.owner) + telefragger = teleporter.owner; + else + telefragger = player; + + makevectors (to_angles); + +#ifdef SVQC + if(player.teleportable == TELEPORT_NORMAL) // don't play sounds or show particles for anything that isn't a player, maybe change later to block only observers + { + if(teleporter.pushltime < time) // only show one teleport effect per teleporter per 0.2 seconds, for better fps + { + if(tflags & TELEPORT_FLAG_SOUND) + { + string thesound = SND(TELEPORT); + if(teleporter.noise != "") + { + RandomSelection_Init(); + FOREACH_WORD(teleporter.noise, true, + { + RandomSelection_AddString(it, 1, 1); + }); + thesound = RandomSelection_chosen_string; + } + _sound (player, CH_TRIGGER, thesound, VOL_BASE, ATTEN_NORM); + } + if(tflags & TELEPORT_FLAG_PARTICLES) + { + Send_Effect(EFFECT_TELEPORT, player.origin, '0 0 0', 1); + Send_Effect(EFFECT_TELEPORT, to + v_forward * 32, '0 0 0', 1); + } + teleporter.pushltime = time + 0.2; + } + } +#endif + + // Relocate the player + // assuming to allows PL_MIN to PL_MAX box and some more +#ifdef SVQC + from = player.origin; + setorigin(player, to); + player.oldorigin = to; // don't undo the teleport by unsticking + player.angles = to_angles; + player.fixangle = true; + player.velocity = to_velocity; + BITXOR_ASSIGN(player.effects, EF_TELEPORT_BIT); + + makevectors(player.angles); + Reset_ArcBeam(player, v_forward); + UpdateCSQCProjectileAfterTeleport(player); + UpdateItemAfterTeleport(player); +#elif defined(CSQC) + from = player.origin; + setorigin(player, to); + player.angles = to_angles; + player.velocity = to_velocity; + UNSET_ONGROUND(player); + player.iflags |= IFLAG_TELEPORTED | IFLAG_V_ANGLE | IFLAG_ANGLES; + player.csqcmodel_teleported = 1; + player.v_angle = to_angles; + + if(player == csqcplayer) // not for anything but the main player + { + setproperty(VF_ANGLES, player.angles); + setproperty(VF_CL_VIEWANGLES, player.angles); + } +#endif + +#ifdef SVQC + if(IS_PLAYER(player)) + { + if(tflags & TELEPORT_FLAG_TDEATH) + if(player.takedamage && !IS_DEAD(player) && !g_race && !g_cts && (autocvar_g_telefrags || (tflags & TELEPORT_FLAG_FORCE_TDEATH))) + tdeath(player, teleporter, telefragger, telefragmin, telefragmax); + + // player no longer is on ground + UNSET_ONGROUND(player); + + // reset tracking of oldvelocity for impact damage (sudden velocity changes) + player.oldvelocity = player.velocity; + + // reset tracking of who pushed you into a hazard (for kill credit) + if(teleporter.owner) + { + player.pusher = teleporter.owner; + player.pushltime = time + autocvar_g_maxpushtime; + player.istypefrag = PHYS_INPUT_BUTTON_CHAT(player); + } + else + { + player.pushltime = 0; + player.istypefrag = 0; + } + + player.lastteleporttime = time; + player.lastteleport_origin = from; + } +#endif +} + +entity Simple_TeleportPlayer(entity teleporter, entity player) +{ + vector locout; + entity e = NULL; + + // Find the output teleporter + if(teleporter.enemy) + { + e = teleporter.enemy; + } + else + { + // sorry CSQC, random stuff ain't gonna happen +#ifdef SVQC + RandomSelection_Init(); + FOREACH_ENTITY_STRING(targetname, teleporter.target, + { + bool p = true; + if(STAT(TELEPORT_TELEFRAG_AVOID, player)) + { + #ifdef SVQC + locout = it.origin + '0 0 1' * (1 - player.mins.z - 24); + #elif defined(CSQC) + locout = it.origin + '0 0 1' * (1 - player.mins.z - 24); + #endif + if(check_tdeath(player, locout, '0 0 0', '0 0 0')) + p = false; + } + RandomSelection_AddEnt(it, (it.cnt ? it.cnt : 1), p); + }); + e = RandomSelection_chosen_ent; +#endif + } + +#ifdef SVQC + if(!e) { sprint(player, "Teleport destination vanished. Sorry... please complain to the mapper.\n"); } +#elif defined(CSQC) + if(!e) { LOG_INFO("Teleport destination could not be found from CSQC."); } +#endif + + makevectors(e.mangle); + + if(e.speed) + if(vdist(player.velocity, >, e.speed)) + player.velocity = normalize(player.velocity) * max(0, e.speed); + + if(STAT(TELEPORT_MAXSPEED, player)) + if(vdist(player.velocity, >, STAT(TELEPORT_MAXSPEED, player))) + player.velocity = normalize(player.velocity) * max(0, STAT(TELEPORT_MAXSPEED, player)); + + locout = e.origin + '0 0 1' * (1 - player.mins.z - 24); + + TeleportPlayer(teleporter, player, locout, e.mangle, v_forward * vlen(player.velocity), '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER); + + return e; +} + +void teleport_findtarget(entity this) +{ + bool istrigger = (this.solid == SOLID_TRIGGER); + + int n = 0; + for(entity e = NULL; (e = find(e, targetname, this.target)); ) + { + ++n; +#ifdef SVQC + if(e.move_movetype == MOVETYPE_NONE) + { + entity tracetest_ent = spawn(); + setsize(tracetest_ent, PL_MIN_CONST, PL_MAX_CONST); + tracetest_ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; + waypoint_spawnforteleporter(this, e.origin, 0, tracetest_ent); + delete(tracetest_ent); + } + if(e.classname != "info_teleport_destination") + LOG_INFO("^3MAPPER ERROR: teleporter does target an invalid teleport destination entity. Angles will not work."); +#endif + } + + if(n == 0) + { + // no dest! + objerror (this, "Teleporter with nonexistant target"); + return; + } + else if(n == 1) + { + // exactly one dest - bots love that + this.enemy = find(NULL, targetname, this.target); + } + else + { + // have to use random selection every single time + this.enemy = NULL; + } + + // now enable touch + if(istrigger) + settouch(this, Teleport_Touch); +#ifdef SVQC + if(istrigger) + trigger_teleport_link(this); +#endif +} + +entity Teleport_Find(vector mi, vector ma) +{ + IL_EACH(g_teleporters, WarpZoneLib_BoxTouchesBrush(mi, ma, it, NULL), + { + return it; + }); + return NULL; +} + +void WarpZone_PostTeleportPlayer_Callback(entity pl) +{ +#ifdef SVQC + makevectors(pl.angles); + Reset_ArcBeam(pl, v_forward); + UpdateCSQCProjectileAfterTeleport(pl); + UpdateItemAfterTeleport(pl); + if (IS_PLAYER(pl)) anticheat_fixangle(pl); +#endif + // "disown" projectiles after teleport + if(pl.owner) + if(pl.owner == pl.realowner) + { + #ifdef SVQC + if(!(pl.flags & FL_PROJECTILE)) + #elif defined(CSQC) + if(!(pl.flags & BIT(15))) // FL_PROJECTILE + #endif + LOG_INFO("A non-projectile got through a warpzone and its owner cleared. It's a ", pl.classname, "."); + pl.owner = NULL; + } + if(IS_PLAYER(pl)) + { + // reset tracking of oldvelocity for impact damage (sudden velocity changes) + #ifdef SVQC + pl.oldvelocity = pl.velocity; + #endif + } +} diff --git a/qcsrc/common/mapobjects/teleporters.qh b/qcsrc/common/mapobjects/teleporters.qh new file mode 100644 index 000000000..68c5114f4 --- /dev/null +++ b/qcsrc/common/mapobjects/teleporters.qh @@ -0,0 +1,72 @@ +#pragma once +#include "defs.qh" + +IntrusiveList g_teleporters; +STATIC_INIT(g_teleporters) { g_teleporters = IL_NEW(); } + +.entity pusher; + +const int TELEPORT_FLAG_SOUND = BIT(0); +const int TELEPORT_FLAG_PARTICLES = BIT(1); +const int TELEPORT_FLAG_TDEATH = BIT(2); +const int TELEPORT_FLAG_FORCE_TDEATH = BIT(3); + +#define TELEPORT_FLAGS_WARPZONE 0 +#define TELEPORT_FLAGS_PORTAL (TELEPORT_FLAG_SOUND | TELEPORT_FLAG_PARTICLES | TELEPORT_FLAG_TDEATH | TELEPORT_FLAG_FORCE_TDEATH) +#define TELEPORT_FLAGS_TELEPORTER (TELEPORT_FLAG_SOUND | TELEPORT_FLAG_PARTICLES | TELEPORT_FLAG_TDEATH) + +// types for .teleportable entity setting +const int TELEPORT_NORMAL = 1; // play sounds/effects etc +const int TELEPORT_SIMPLE = 2; // only do teleport, nothing special + +entity Simple_TeleportPlayer(entity teleporter, entity player); + +void Teleport_Touch(entity this, entity toucher); + +void teleport_findtarget(entity this); + +entity Teleport_Find(vector mi, vector ma); + +void TeleportPlayer(entity teleporter, entity player, vector to, vector to_angles, vector to_velocity, vector telefragmin, vector telefragmax, float tflags); + +#ifdef SVQC + +void trigger_teleport_use(entity this, entity actor, entity trigger); + +#define TDEATHLOOP(o) \ + entity head; \ + vector deathmin; \ + vector deathmax; \ + float deathradius; \ + deathmin = (o) + player.mins; \ + deathmax = (o) + player.maxs; \ + if(telefragmin != telefragmax) \ + { \ + if(deathmin.x > telefragmin.x) deathmin.x = telefragmin.x; \ + if(deathmin.y > telefragmin.y) deathmin.y = telefragmin.y; \ + if(deathmin.z > telefragmin.z) deathmin.z = telefragmin.z; \ + if(deathmax.x < telefragmax.x) deathmax.x = telefragmax.x; \ + if(deathmax.y < telefragmax.y) deathmax.y = telefragmax.y; \ + if(deathmax.z < telefragmax.z) deathmax.z = telefragmax.z; \ + } \ + deathradius = max(vlen(deathmin), vlen(deathmax)); \ + for(head = findradius(o, deathradius); head; head = head.chain) \ + if(head != player) \ + if(head.takedamage) \ + if(boxesoverlap(deathmin, deathmax, head.absmin, head.absmax)) + +float check_tdeath(entity player, vector org, vector telefragmin, vector telefragmax); +float tdeath_hit; +void tdeath(entity player, entity teleporter, entity telefragger, vector telefragmin, vector telefragmax); + +void spawn_tdeath(vector v0, entity e, vector v); + +void Reset_ArcBeam(entity player, vector forward); + +#endif + +void WarpZone_PostTeleportPlayer_Callback(entity pl); + +#ifdef CSQC +.entity realowner; +#endif diff --git a/qcsrc/common/mapobjects/trigger/_mod.inc b/qcsrc/common/mapobjects/trigger/_mod.inc new file mode 100644 index 000000000..c2a663f05 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/_mod.inc @@ -0,0 +1,24 @@ +// generated file; do not modify +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/qcsrc/common/mapobjects/trigger/_mod.qh b/qcsrc/common/mapobjects/trigger/_mod.qh new file mode 100644 index 000000000..08a6e5a06 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/_mod.qh @@ -0,0 +1,24 @@ +// generated file; do not modify +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/qcsrc/common/mapobjects/trigger/counter.qc b/qcsrc/common/mapobjects/trigger/counter.qc new file mode 100644 index 000000000..4c89c4c27 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/counter.qc @@ -0,0 +1,67 @@ +#include "counter.qh" +#ifdef SVQC +void counter_reset(entity this); + +void counter_use(entity this, entity actor, entity trigger) +{ + this.count -= 1; + if (this.count < 0) + return; + + bool doactivate = (this.spawnflags & COUNTER_FIRE_AT_COUNT); + + if (this.count == 0) + { + if(IS_PLAYER(actor) && !(this.spawnflags & SPAWNFLAG_NOMESSAGE)) + Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COMPLETED); + + doactivate = true; + + if(this.respawntime) + { + setthink(this, counter_reset); + this.nextthink = time + this.respawntime; + } + } + else + { + if(IS_PLAYER(actor) && !(this.spawnflags & SPAWNFLAG_NOMESSAGE)) + { + if(this.count >= 4) + Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COUNTER); + else + Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, this.count); + } + } + + if(doactivate) + SUB_UseTargets(this, actor, trigger); +} + +void counter_reset(entity this) +{ + setthink(this, func_null); + this.nextthink = 0; + this.count = this.cnt; +} + +/*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage COUNTER_FIRE_AT_COUNT +Acts as an intermediary for an action that takes multiple inputs. + +If nomessage is not set, it will print "1 more.. " etc when triggered and "sequence complete" when finished. +If COUNTER_FIRE_AT_COUNT is set, it will also fire all of its targets at countdown, making it behave like trigger_mulitple with limited shots + +If respawntime is set, it will re-enable itself after the time once the sequence has been completed + +After the counter has been triggered "count" times (default 2), it will fire all of its targets. +*/ +spawnfunc(trigger_counter) +{ + if (!this.count) + this.count = 2; + this.cnt = this.count; + + this.use = counter_use; + this.reset = counter_reset; +} +#endif diff --git a/qcsrc/common/mapobjects/trigger/counter.qh b/qcsrc/common/mapobjects/trigger/counter.qh new file mode 100644 index 000000000..394d15472 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/counter.qh @@ -0,0 +1,4 @@ +#pragma once + + +const int COUNTER_FIRE_AT_COUNT = BIT(2); diff --git a/qcsrc/common/mapobjects/trigger/delay.qc b/qcsrc/common/mapobjects/trigger/delay.qc new file mode 100644 index 000000000..2cd4cfd13 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/delay.qc @@ -0,0 +1,32 @@ +#include "delay.qh" +#ifdef SVQC +void delay_delayeduse(entity this) +{ + SUB_UseTargets(this, this.enemy, this.goalentity); + this.enemy = this.goalentity = NULL; +} + +void delay_use(entity this, entity actor, entity trigger) +{ + this.enemy = actor; + this.goalentity = trigger; + setthink(this, delay_delayeduse); + this.nextthink = time + this.wait; +} + +void delay_reset(entity this) +{ + this.enemy = this.goalentity = NULL; + setthink(this, func_null); + this.nextthink = 0; +} + +spawnfunc(trigger_delay) +{ + if(!this.wait) + this.wait = 1; + + this.use = delay_use; + this.reset = delay_reset; +} +#endif diff --git a/qcsrc/common/mapobjects/trigger/delay.qh b/qcsrc/common/mapobjects/trigger/delay.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/delay.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/trigger/disablerelay.qc b/qcsrc/common/mapobjects/trigger/disablerelay.qc new file mode 100644 index 000000000..eee61c993 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/disablerelay.qc @@ -0,0 +1,29 @@ +#include "disablerelay.qh" +#ifdef SVQC +void trigger_disablerelay_use(entity this, entity actor, entity trigger) +{ + int a = 0, b = 0; + + for(entity e = NULL; (e = find(e, targetname, this.target)); ) + { + if(e.use == SUB_UseTargets) + { + e.use = SUB_DontUseTargets; + ++a; + } + else if(e.use == SUB_DontUseTargets) + { + e.use = SUB_UseTargets; + ++b; + } + } + + if((!a) == (!b)) + LOG_INFO("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!"); +} + +spawnfunc(trigger_disablerelay) +{ + this.use = trigger_disablerelay_use; +} +#endif diff --git a/qcsrc/common/mapobjects/trigger/disablerelay.qh b/qcsrc/common/mapobjects/trigger/disablerelay.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/disablerelay.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/trigger/flipflop.qc b/qcsrc/common/mapobjects/trigger/flipflop.qc new file mode 100644 index 000000000..141f3ea9f --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/flipflop.qc @@ -0,0 +1,23 @@ +#include "flipflop.qh" +#ifdef SVQC +/*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED +"Flip-flop" trigger gate... lets only every second trigger event through +*/ +void flipflop_use(entity this, entity actor, entity trigger) +{ + this.state = !this.state; + if(this.state) + SUB_UseTargets(this, actor, trigger); +} + +spawnfunc(trigger_flipflop) +{ + if(this.spawnflags & START_ENABLED) + { + this.state = true; + } + this.use = flipflop_use; + this.reset = spawnfunc_trigger_flipflop; // perfect resetter +} + +#endif diff --git a/qcsrc/common/mapobjects/trigger/flipflop.qh b/qcsrc/common/mapobjects/trigger/flipflop.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/flipflop.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/trigger/gamestart.qc b/qcsrc/common/mapobjects/trigger/gamestart.qc new file mode 100644 index 000000000..72d76d183 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/gamestart.qc @@ -0,0 +1,28 @@ +#include "gamestart.qh" +#ifdef SVQC +void gamestart_use(entity this, entity actor, entity trigger) +{ + SUB_UseTargets(this, this, trigger); + delete(this); +} + +void gamestart_use_this(entity this) +{ + gamestart_use(this, NULL, NULL); +} + +spawnfunc(trigger_gamestart) +{ + this.use = gamestart_use; + this.reset2 = spawnfunc_trigger_gamestart; + + if(this.wait) + { + setthink(this, adaptor_think2use); + this.nextthink = game_starttime + this.wait; + } + else + InitializeEntity(this, gamestart_use_this, INITPRIO_FINDTARGET); +} + +#endif diff --git a/qcsrc/common/mapobjects/trigger/gamestart.qh b/qcsrc/common/mapobjects/trigger/gamestart.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/gamestart.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/trigger/gravity.qc b/qcsrc/common/mapobjects/trigger/gravity.qc new file mode 100644 index 000000000..1ac0f8768 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/gravity.qc @@ -0,0 +1,111 @@ +#include "gravity.qh" +#ifdef SVQC +.entity trigger_gravity_check; +void trigger_gravity_remove(entity own) +{ + if(own.trigger_gravity_check.owner == own) + { + UpdateCSQCProjectile(own); + own.gravity = own.trigger_gravity_check.gravity; + delete(own.trigger_gravity_check); + } + else + backtrace("Removing a trigger_gravity_check with no valid owner"); + own.trigger_gravity_check = NULL; +} +void trigger_gravity_check_think(entity this) +{ + // This spawns when a player enters the gravity zone and checks if he left. + // Each frame, this.count is set to 2 by trigger_gravity_touch() and decreased by 1 here. + // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that. + if(this.count <= 0) + { + if(this.owner.trigger_gravity_check == this) + trigger_gravity_remove(this.owner); + else + delete(this); + return; + } + else + { + this.count -= 1; + this.nextthink = time; + } +} + +// legacy +void trigger_gravity_use(entity this, entity actor, entity trigger) +{ + this.setactive(this, ACTIVE_TOGGLE); +} + +void trigger_gravity_touch(entity this, entity toucher) +{ + float g; + + if(this.active == ACTIVE_NOT) + return; + + EXACTTRIGGER_TOUCH(this, toucher); + + g = this.gravity; + + if (!(this.spawnflags & GRAVITY_STICKY)) + { + if(toucher.trigger_gravity_check) + { + if(this == toucher.trigger_gravity_check.enemy) + { + // same? + // NOTE: see explanation in trigger_gravity_check_think + toucher.trigger_gravity_check.count = 2; // gravity one more frame... + return; + } + + // compare prio + if(this.cnt > toucher.trigger_gravity_check.enemy.cnt) + trigger_gravity_remove(toucher); + else + return; + } + toucher.trigger_gravity_check = spawn(); + toucher.trigger_gravity_check.enemy = this; + toucher.trigger_gravity_check.owner = toucher; + toucher.trigger_gravity_check.gravity = toucher.gravity; + setthink(toucher.trigger_gravity_check, trigger_gravity_check_think); + toucher.trigger_gravity_check.nextthink = time; + toucher.trigger_gravity_check.count = 2; + if(toucher.gravity) + g *= toucher.gravity; + } + + if (toucher.gravity != g) + { + toucher.gravity = g; + if(this.noise != "") + _sound (toucher, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM); + UpdateCSQCProjectile(this.owner); + } +} + +spawnfunc(trigger_gravity) +{ + if(this.gravity == 1) + return; + + EXACTTRIGGER_INIT; + settouch(this, trigger_gravity_touch); + if(this.noise != "") + precache_sound(this.noise); + + this.active = ACTIVE_ACTIVE; + this.setactive = generic_setactive; + IFTARGETED + { + // legacy use + this.use = trigger_gravity_use; + if(this.spawnflags & GRAVITY_START_DISABLED) + this.active = ACTIVE_NOT; + } +} +#endif diff --git a/qcsrc/common/mapobjects/trigger/gravity.qh b/qcsrc/common/mapobjects/trigger/gravity.qh new file mode 100644 index 000000000..872f04ad9 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/gravity.qh @@ -0,0 +1,5 @@ +#pragma once + + +const int GRAVITY_STICKY = BIT(0); // keep gravity multiplier even after exiting the trigger_gravity +const int GRAVITY_START_DISABLED = BIT(1); diff --git a/qcsrc/common/mapobjects/trigger/heal.qc b/qcsrc/common/mapobjects/trigger/heal.qc new file mode 100644 index 000000000..cfcd726fc --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/heal.qc @@ -0,0 +1,65 @@ +#include "heal.qh" +#ifdef SVQC +.float triggerhealtime; +void trigger_heal_touch(entity this, entity toucher) +{ + if (this.active != ACTIVE_ACTIVE) + return; + + // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu) + if (toucher.iscreature) + { + if (toucher.takedamage && !IS_DEAD(toucher) && toucher.triggerhealtime < time) + { + bool is_trigger = this.targetname == ""; + if(is_trigger) + EXACTTRIGGER_TOUCH(this, toucher); + if(this.delay > 0) + toucher.triggerhealtime = time + this.delay; + + bool playthesound = (this.spawnflags & HEAL_SOUND_ALWAYS); + if (toucher.health < this.max_health) + { + playthesound = true; + toucher.health = min(toucher.health + this.health, this.max_health); + toucher.pauserothealth_finished = max(toucher.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot); + } + + if(playthesound) + _sound (toucher, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM); + } + } +} + +void trigger_heal_use(entity this, entity actor, entity trigger) +{ + trigger_heal_touch(this, actor); +} + +void trigger_heal_init(entity this) +{ + this.active = ACTIVE_ACTIVE; + if(!this.delay) + this.delay = 1; + if(!this.health) + this.health = 10; + if(!this.max_health) + this.max_health = 200; // max health topoff for field + if(this.noise == "") + this.noise = "misc/mediumhealth.wav"; + precache_sound(this.noise); +} + +spawnfunc(trigger_heal) +{ + EXACTTRIGGER_INIT; + settouch(this, trigger_heal_touch); + trigger_heal_init(this); +} + +spawnfunc(target_heal) +{ + this.use = trigger_heal_use; + trigger_heal_init(this); +} +#endif diff --git a/qcsrc/common/mapobjects/trigger/heal.qh b/qcsrc/common/mapobjects/trigger/heal.qh new file mode 100644 index 000000000..8dbeea545 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/heal.qh @@ -0,0 +1,4 @@ +#pragma once + + +const int HEAL_SOUND_ALWAYS = BIT(2); diff --git a/qcsrc/common/mapobjects/trigger/hurt.qc b/qcsrc/common/mapobjects/trigger/hurt.qc new file mode 100644 index 000000000..966e0cfb0 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/hurt.qc @@ -0,0 +1,93 @@ +#include "hurt.qh" +#ifdef SVQC +void trigger_hurt_use(entity this, entity actor, entity trigger) +{ + if(IS_PLAYER(actor)) + this.enemy = actor; + else + this.enemy = NULL; // let's just destroy it, if taking over is too much work +} + +.float triggerhurttime; +void trigger_hurt_touch(entity this, entity toucher) +{ + if (this.active != ACTIVE_ACTIVE) + return; + + if(this.team) + if(((this.spawnflags & INVERT_TEAMS) == 0) == (this.team != toucher.team)) + return; + + // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu) + if (toucher.iscreature) + { + if (toucher.takedamage) + if (toucher.triggerhurttime < time) + { + EXACTTRIGGER_TOUCH(this, toucher); + toucher.triggerhurttime = time + 1; + + entity own; + own = this.enemy; + if (!IS_PLAYER(own)) + { + own = this; + this.enemy = NULL; // I still hate you all + } + + Damage (toucher, this, own, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, toucher.origin, '0 0 0'); + } + } + else if(toucher.damagedbytriggers) + { + if(toucher.takedamage) + { + EXACTTRIGGER_TOUCH(this, toucher); + Damage(toucher, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, toucher.origin, '0 0 0'); + } + } + + return; +} + +/*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ? +Any object touching this will be hurt +set dmg to damage amount +default dmg = 1000 +*/ +.entity trigger_hurt_next; +entity trigger_hurt_last; +entity trigger_hurt_first; +spawnfunc(trigger_hurt) +{ + EXACTTRIGGER_INIT; + this.active = ACTIVE_ACTIVE; + settouch(this, trigger_hurt_touch); + this.use = trigger_hurt_use; + this.enemy = world; // I hate you all + if (!this.dmg) + this.dmg = 1000; + if (this.message == "") + this.message = "was in the wrong place"; + if (this.message2 == "") + this.message2 = "was thrown into a world of hurt by"; + // this.message = "someone like %s always gets wrongplaced"; + + if(!trigger_hurt_first) + trigger_hurt_first = this; + if(trigger_hurt_last) + trigger_hurt_last.trigger_hurt_next = this; + trigger_hurt_last = this; +} + +bool tracebox_hits_trigger_hurt(vector start, vector e_min, vector e_max, vector end) +{ + entity th; + + for(th = trigger_hurt_first; th; th = th.trigger_hurt_next) + if(tracebox_hits_box(start, e_min, e_max, end, th.absmin, th.absmax)) + return true; + + return false; +} +#endif diff --git a/qcsrc/common/mapobjects/trigger/hurt.qh b/qcsrc/common/mapobjects/trigger/hurt.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/hurt.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/trigger/impulse.qc b/qcsrc/common/mapobjects/trigger/impulse.qc new file mode 100644 index 000000000..c4e7ae287 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/impulse.qc @@ -0,0 +1,243 @@ +#include "impulse.qh" +// targeted (directional) mode +void trigger_impulse_touch_directional(entity this, entity toucher) +{ + entity targ; + float pushdeltatime; + float str; + + if (this.active != ACTIVE_ACTIVE) + return; + + if (!isPushable(toucher)) + return; + + EXACTTRIGGER_TOUCH(this, toucher); + + targ = find(NULL, targetname, this.target); + if(!targ) + { + objerror(this, "trigger_force without a (valid) .target!\n"); + delete(this); + return; + } + + // falloff is not supported because radius is always 0 in directional mode + str = this.strength; + + pushdeltatime = time - toucher.lastpushtime; + if (pushdeltatime > IMPULSE_MAX_PUSHDELTATIME) + { + pushdeltatime = 0; + } + toucher.lastpushtime = time; + if(!pushdeltatime) + { + return; + } + + if(this.spawnflags & IMPULSE_DIRECTIONAL_SPEEDTARGET) + { + float addspeed = str - toucher.velocity * normalize(targ.origin - this.origin); + if (addspeed > 0) + { + float accelspeed = min(IMPULSE_DIRECTIONAL_MAX_ACCEL_FACTOR * pushdeltatime * str, addspeed); + toucher.velocity += accelspeed * normalize(targ.origin - this.origin); + } + } + else + toucher.velocity = toucher.velocity + normalize(targ.origin - this.origin) * str * pushdeltatime; + + UNSET_ONGROUND(toucher); + +#ifdef SVQC + UpdateCSQCProjectile(toucher); +#endif +} + +// Directionless (accelerator/decelerator) mode +void trigger_impulse_touch_accel(entity this, entity toucher) +{ + float pushdeltatime; + + if (this.active != ACTIVE_ACTIVE) + return; + + if (!isPushable(toucher)) + return; + + EXACTTRIGGER_TOUCH(this, toucher); + + pushdeltatime = time - toucher.lastpushtime; + if (pushdeltatime > IMPULSE_MAX_PUSHDELTATIME) + { + pushdeltatime = 0; + } + toucher.lastpushtime = time; + if(!pushdeltatime) + { + return; + } + + // div0: ticrate independent, 1 = identity (not 20) + toucher.velocity = toucher.velocity * (this.strength ** pushdeltatime); + +#ifdef SVQC + UpdateCSQCProjectile(toucher); +#endif +} + +// Spherical (gravity/repulsor) mode +void trigger_impulse_touch_radial(entity this, entity toucher) +{ + float pushdeltatime; + float str; + + if (this.active != ACTIVE_ACTIVE) + return; + + if (!isPushable(toucher)) + return; + + EXACTTRIGGER_TOUCH(this, toucher); + + pushdeltatime = time - toucher.lastpushtime; + if (pushdeltatime > IMPULSE_MAX_PUSHDELTATIME) + { + pushdeltatime = 0; + } + toucher.lastpushtime = time; + if(!pushdeltatime) + { + return; + } + + setsize(this, '-1 -1 -1' * this.radius,'1 1 1' * this.radius); + + str = min(this.radius, vlen(this.origin - toucher.origin)); + + if(this.falloff == FALLOFF_LINEAR) + str = (1 - str / this.radius) * this.strength; // 1 in the inside + else if(this.falloff == FALLOFF_LINEAR_INV) + str = (str / this.radius) * this.strength; // 0 in the inside + else + str = this.strength; + + toucher.velocity = toucher.velocity + normalize(toucher.origin - this.origin) * str * pushdeltatime; + +#ifdef SVQC + UpdateCSQCProjectile(toucher); +#endif +} + +REGISTER_NET_LINKED(ENT_CLIENT_TRIGGER_IMPULSE) + +/*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ? +Force field +-------- KEYS -------- +target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed. + If not, this trigger acts like a damper/accelerator field. + +strength : This is how much force to add in the direction of .target each second + when .target is set. If not, this is how much to slow down/accelerate + something cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble) + +radius : If set, act as a spherical device rather then a linear one. + +falloff : 0 = none, 1 = liniar, 2 = inverted liniar + +-------- NOTES -------- +Use a brush textured with common/origin in the trigger entity to determine the origin of the force +in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect). +*/ +#ifdef SVQC +bool trigger_impulse_send(entity this, entity to, int sf) +{ + WriteHeader(MSG_ENTITY, ENT_CLIENT_TRIGGER_IMPULSE); + + WriteByte(MSG_ENTITY, this.spawnflags); + WriteCoord(MSG_ENTITY, this.radius); + WriteCoord(MSG_ENTITY, this.strength); + WriteByte(MSG_ENTITY, this.falloff); + WriteByte(MSG_ENTITY, this.active); + + trigger_common_write(this, true); + + return true; +} + +void trigger_impulse_link(entity this) +{ + trigger_link(this, trigger_impulse_send); +} + +spawnfunc(trigger_impulse) +{ + this.active = ACTIVE_ACTIVE; + + trigger_init(this); + + if(this.radius) + { + if(!this.strength) + { + this.strength = IMPULSE_DEFAULT_RADIAL_STRENGTH * autocvar_g_triggerimpulse_radial_multiplier; + } + setorigin(this, this.origin); + setsize(this, '-1 -1 -1' * this.radius,'1 1 1' * this.radius); + settouch(this, trigger_impulse_touch_radial); + } + else + { + if(this.target) + { + if(!this.strength) + { + this.strength = IMPULSE_DEFAULT_DIRECTIONAL_STRENGTH * autocvar_g_triggerimpulse_directional_multiplier; + } + settouch(this, trigger_impulse_touch_directional); + } + else + { + if(!this.strength) + { + this.strength = IMPULSE_DEFAULT_ACCEL_STRENGTH; + } + this.strength = (this.strength ** autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier; + settouch(this, trigger_impulse_touch_accel); + } + } + + trigger_impulse_link(this); +} +#elif defined(CSQC) +NET_HANDLE(ENT_CLIENT_TRIGGER_IMPULSE, bool isnew) +{ + this.spawnflags = ReadByte(); + this.radius = ReadCoord(); + this.strength = ReadCoord(); + this.falloff = ReadByte(); + this.active = ReadByte(); + + trigger_common_read(this, true); + return = true; + + this.classname = "trigger_impulse"; + this.solid = SOLID_TRIGGER; + this.entremove = trigger_remove_generic; + this.move_time = time; + + if (this.radius) + { + settouch(this, trigger_impulse_touch_radial); + } + else if (this.target) + { + settouch(this, trigger_impulse_touch_directional); + } + else + { + settouch(this, trigger_impulse_touch_accel); + } +} +#endif diff --git a/qcsrc/common/mapobjects/trigger/impulse.qh b/qcsrc/common/mapobjects/trigger/impulse.qh new file mode 100644 index 000000000..e86de4a49 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/impulse.qh @@ -0,0 +1,21 @@ +#pragma once + +// tZorks trigger impulse / gravity +.float radius; +.int falloff; +.float strength; +.float lastpushtime; + +const int FALLOFF_NO = 0; +const int FALLOFF_LINEAR = 1; +const int FALLOFF_LINEAR_INV = 2; + +const int IMPULSE_DIRECTIONAL_SPEEDTARGET = BIT(6); + +const float IMPULSE_DEFAULT_RADIAL_STRENGTH = 2000; +const float IMPULSE_DEFAULT_DIRECTIONAL_STRENGTH = 950; +const float IMPULSE_DEFAULT_ACCEL_STRENGTH = 0.9; + +const float IMPULSE_MAX_PUSHDELTATIME = 0.15; + +const float IMPULSE_DIRECTIONAL_MAX_ACCEL_FACTOR = 8; diff --git a/qcsrc/common/mapobjects/trigger/jumppads.qc b/qcsrc/common/mapobjects/trigger/jumppads.qc new file mode 100644 index 000000000..5ffdf2d10 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/jumppads.qc @@ -0,0 +1,676 @@ +#include "jumppads.qh" +// TODO: split target_push and put it in the target folder +#ifdef SVQC +#include + +void trigger_push_use(entity this, entity actor, entity trigger) +{ + if(teamplay) + { + this.team = actor.team; + this.SendFlags |= SF_TRIGGER_UPDATE; + } +} +#endif + +REGISTER_NET_LINKED(ENT_CLIENT_TRIGGER_PUSH) +REGISTER_NET_LINKED(ENT_CLIENT_TARGET_PUSH) + +/* + trigger_push_calculatevelocity + + Arguments: + org - origin of the object which is to be pushed + tgt - target entity (can be either a point or a model entity; if it is + the latter, its midpoint is used) + ht - jump height, measured from the higher one of org and tgt's midpoint + pushed_entity - object that is to be pushed + + Returns: velocity for the jump + */ +vector trigger_push_calculatevelocity(vector org, entity tgt, float ht, entity pushed_entity) +{ + float grav, sdist, zdist, vs, vz, jumpheight; + vector sdir, torg; + + torg = tgt.origin + (tgt.mins + tgt.maxs) * 0.5; + + grav = PHYS_GRAVITY(NULL); + if(pushed_entity && PHYS_ENTGRAVITY(pushed_entity)) + grav *= PHYS_ENTGRAVITY(pushed_entity); + + zdist = torg.z - org.z; + sdist = vlen(torg - org - zdist * '0 0 1'); + sdir = normalize(torg - org - zdist * '0 0 1'); + + // how high do we need to push the player? + jumpheight = fabs(ht); + if(zdist > 0) + jumpheight = jumpheight + zdist; + + /* + STOP. + + You will not understand the following equations anyway... + But here is what I did to get them. + + I used the functions + + s(t) = t * vs + z(t) = t * vz - 1/2 grav t^2 + + and solved for: + + s(ti) = sdist + z(ti) = zdist + max(z, ti) = jumpheight + + From these three equations, you will find the three parameters vs, vz + and ti. + */ + + // push him so high... + vz = sqrt(fabs(2 * grav * jumpheight)); // NOTE: sqrt(positive)! + + // we start with downwards velocity only if it's a downjump and the jump apex should be outside the jump! + if(ht < 0) + if(zdist < 0) + vz = -vz; + + vector solution; + solution = solve_quadratic(0.5 * grav, -vz, zdist); // equation "z(ti) = zdist" + // ALWAYS solvable because jumpheight >= zdist + if(!solution.z) + solution_y = solution.x; // just in case it is not solvable due to roundoff errors, assume two equal solutions at their center (this is mainly for the usual case with ht == 0) + if(zdist == 0) + solution_x = solution.y; // solution_x is 0 in this case, so don't use it, but rather use solution_y (which will be sqrt(0.5 * jumpheight / grav), actually) + + float flighttime; + if(zdist < 0) + { + // down-jump + if(ht < 0) + { + // almost straight line type + // jump apex is before the jump + // we must take the larger one + flighttime = solution.y; + } + else + { + // regular jump + // jump apex is during the jump + // we must take the larger one too + flighttime = solution.y; + } + } + else + { + // up-jump + if(ht < 0) + { + // almost straight line type + // jump apex is after the jump + // we must take the smaller one + flighttime = solution.x; + } + else + { + // regular jump + // jump apex is during the jump + // we must take the larger one + flighttime = solution.y; + } + } + vs = sdist / flighttime; + + // finally calculate the velocity + return sdir * vs + '0 0 1' * vz; +} + +bool jumppad_push(entity this, entity targ) +{ + if (!isPushable(targ)) + return false; + + if(this.enemy) + { + targ.velocity = trigger_push_calculatevelocity(targ.origin, this.enemy, this.height, targ); + } + else if(this.target && this.target != "") + { + entity e; + RandomSelection_Init(); + for(e = NULL; (e = find(e, targetname, this.target)); ) + { + if(e.cnt) + RandomSelection_AddEnt(e, e.cnt, 1); + else + RandomSelection_AddEnt(e, 1, 1); + } + targ.velocity = trigger_push_calculatevelocity(targ.origin, RandomSelection_chosen_ent, this.height, targ); + } + else + { + targ.velocity = this.movedir; + } + + UNSET_ONGROUND(targ); + +#ifdef CSQC + if (targ.flags & FL_PROJECTILE) + { + targ.angles = vectoangles (targ.velocity); + switch(targ.move_movetype) + { + case MOVETYPE_FLY: + set_movetype(targ, MOVETYPE_TOSS); + targ.gravity = 1; + break; + case MOVETYPE_BOUNCEMISSILE: + set_movetype(targ, MOVETYPE_BOUNCE); + targ.gravity = 1; + break; + } + } +#endif + +#ifdef SVQC + if (IS_PLAYER(targ)) + { + // reset tracking of oldvelocity for impact damage (sudden velocity changes) + targ.oldvelocity = targ.velocity; + + if(this.pushltime < time) // prevent "snorring" sound when a player hits the jumppad more than once + { + // flash when activated + Send_Effect(EFFECT_JUMPPAD, targ.origin, targ.velocity, 1); + _sound (targ, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM); + this.pushltime = time + 0.2; + } + if(IS_REAL_CLIENT(targ) || IS_BOT_CLIENT(targ)) + { + bool found = false; + for(int i = 0; i < targ.jumppadcount && i < NUM_JUMPPADSUSED; ++i) + if(targ.(jumppadsused[i]) == this) + found = true; + if(!found) + { + targ.(jumppadsused[targ.jumppadcount % NUM_JUMPPADSUSED]) = this; + targ.jumppadcount = targ.jumppadcount + 1; + } + + if(IS_REAL_CLIENT(targ)) + { + if(this.message) + centerprint(targ, this.message); + } + else + { + targ.lastteleporttime = time; + targ.lastteleport_origin = targ.origin; + } + + if (!IS_DEAD(targ)) + animdecide_setaction(targ, ANIMACTION_JUMP, true); + } + else + targ.jumppadcount = 1; + + // reset tracking of who pushed you into a hazard (for kill credit) + targ.pushltime = 0; + targ.istypefrag = 0; + } + + if(this.enemy.target) + SUB_UseTargets(this.enemy, targ, this); + + if (targ.flags & FL_PROJECTILE) + { + targ.angles = vectoangles (targ.velocity); + targ.com_phys_gravity_factor = 1; + switch(targ.move_movetype) + { + case MOVETYPE_FLY: + set_movetype(targ, MOVETYPE_TOSS); + targ.gravity = 1; + break; + case MOVETYPE_BOUNCEMISSILE: + set_movetype(targ, MOVETYPE_BOUNCE); + targ.gravity = 1; + break; + } + UpdateCSQCProjectile(targ); + } +#endif + + return true; +} + +void trigger_push_touch(entity this, entity toucher) +{ + if (this.active == ACTIVE_NOT) + return; + + if(this.team) + if(((this.spawnflags & INVERT_TEAMS) == 0) == (DIFF_TEAM(this, toucher))) + return; + + EXACTTRIGGER_TOUCH(this, toucher); + + noref bool success = jumppad_push(this, toucher); + +#ifdef SVQC + if (success && (this.spawnflags & PUSH_ONCE)) + { + settouch(this, func_null); + setthink(this, SUB_Remove); + this.nextthink = time; + } +#endif +} + +#ifdef SVQC +void trigger_push_link(entity this); +void trigger_push_updatelink(entity this); +bool trigger_push_testorigin(entity tracetest_ent, entity targ, entity jp, vector org) +{ + setorigin(tracetest_ent, org); + tracetoss(tracetest_ent, tracetest_ent); + if(trace_startsolid) + return false; + + if (!jp.height) + { + // since tracetoss starting from jumppad's origin often fails when target + // is very close to real destination, start it directly from target's + // origin instead + vector ofs = '0 0 0'; + if (vdist(vec2(tracetest_ent.velocity), <, autocvar_sv_maxspeed)) + ofs = stepheightvec; + + tracetest_ent.velocity.z = 0; + setorigin(tracetest_ent, targ.origin + ofs); + tracetoss(tracetest_ent, tracetest_ent); + if (trace_startsolid && ofs.z) + { + setorigin(tracetest_ent, targ.origin + ofs / 2); + tracetoss(tracetest_ent, tracetest_ent); + if (trace_startsolid && ofs.z) + { + setorigin(tracetest_ent, targ.origin); + tracetoss(tracetest_ent, tracetest_ent); + if (trace_startsolid) + return false; + } + } + } + tracebox(trace_endpos, tracetest_ent.mins, tracetest_ent.maxs, trace_endpos - eZ * 1500, true, tracetest_ent); + return true; +} + +bool trigger_push_testorigin_for_item(entity tracetest_ent, entity item, vector org) +{ + setorigin(tracetest_ent, org); + tracetoss(tracetest_ent, tracetest_ent); + + if(trace_startsolid) + return false; + if (trace_ent == item) + return true; + + tracebox(trace_endpos, tracetest_ent.mins, tracetest_ent.maxs, trace_endpos - eZ * 1500, true, tracetest_ent); + + if (trace_ent == item) + return true; + + return false; +} +#endif + +/// if (item != NULL) returns true if the item can be reached by using this jumppad, false otherwise +/// if (item == NULL) tests jumppad's trajectory and eventually spawns waypoints for it (return value doesn't matter) +bool trigger_push_test(entity this, entity item) +{ + // first calculate a typical start point for the jump + vector org = (this.absmin + this.absmax) * 0.5; + org.z = this.absmax.z - PL_MIN_CONST.z - 7; + + if (this.target) + { + int n = 0; +#ifdef SVQC + vector vel = '0 0 0'; +#endif + for(entity t = NULL; (t = find(t, targetname, this.target)); ) + { + ++n; +#ifdef SVQC + if(t.move_movetype != MOVETYPE_NONE) + continue; + + entity e = spawn(); + setsize(e, PL_MIN_CONST, PL_MAX_CONST); + e.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; + e.velocity = trigger_push_calculatevelocity(org, t, this.height, e); + + vel = e.velocity; + vector best_target = '0 0 0'; + vector best_org = '0 0 0'; + vector best_vel = '0 0 0'; + bool valid_best_target = false; + if (item) + { + if (!trigger_push_testorigin_for_item(e, item, org)) + { + delete(e); + return false; + } + } + else + { + if (trigger_push_testorigin(e, t, this, org)) + { + best_target = trace_endpos; + best_org = org; + best_vel = e.velocity; + valid_best_target = true; + } + } + + vector new_org; + vector dist = t.origin - org; + if (dist.x || dist.y) // if not perfectly vertical + { + // test trajectory with different starting points, sometimes the trajectory + // starting from the jumppad origin can't reach the real destination + // and destination waypoint ends up near the jumppad itself + vector flatdir = normalize(dist - eZ * dist.z); + vector ofs = flatdir * 0.5 * min(fabs(this.absmax.x - this.absmin.x), fabs(this.absmax.y - this.absmin.y)); + new_org = org + ofs; + + LABEL(new_test) + e.velocity = trigger_push_calculatevelocity(new_org, t, this.height, e); + if (item) + { + if (!trigger_push_testorigin_for_item(e, item, new_org)) + { + delete(e); + return false; + } + } + else + { + vel = e.velocity; + if (vdist(vec2(e.velocity), <, autocvar_sv_maxspeed)) + e.velocity = autocvar_sv_maxspeed * flatdir; + if (trigger_push_testorigin(e, t, this, new_org) && (!valid_best_target || trace_endpos.z > best_target.z + 50)) + { + best_target = trace_endpos; + best_org = new_org; + best_vel = vel; + valid_best_target = true; + } + } + if (ofs && new_org != org - ofs) + { + new_org = org - ofs; + goto new_test; + } + } + + if (item) + { + delete(e); + return true; + } + + if (valid_best_target) + { + if (!(boxesoverlap(this.absmin, this.absmax + eZ * 50, best_target + PL_MIN_CONST, best_target + PL_MAX_CONST))) + { + float velxy = vlen(vec2(best_vel)); + float cost = vlen(vec2(t.origin - best_org)) / velxy; + if(velxy < autocvar_sv_maxspeed) + velxy = autocvar_sv_maxspeed; + cost += vlen(vec2(best_target - t.origin)) / velxy; + waypoint_spawnforteleporter(this, best_target, cost, e); + } + } + delete(e); +#endif + } + + if(item) + return false; + + if(!n) + { + // no dest! +#ifdef SVQC + objerror (this, "Jumppad with nonexistant target"); +#endif + return false; + } + else if(n == 1) + { + // exactly one dest - bots love that + this.enemy = find(NULL, targetname, this.target); + } + else + { + // have to use random selection every single time + this.enemy = NULL; + } + } +#ifdef SVQC + else + { + entity e = spawn(); + setsize(e, PL_MIN_CONST, PL_MAX_CONST); + e.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; + setorigin(e, org); + e.velocity = this.movedir; + tracetoss(e, e); + if (item) + { + bool r = (trace_ent == item); + delete(e); + return r; + } + if (!(boxesoverlap(this.absmin, this.absmax + eZ * 50, trace_endpos + PL_MIN_CONST, trace_endpos + PL_MAX_CONST))) + waypoint_spawnforteleporter(this, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity), e); + delete(e); + } + + defer(this, 0.1, trigger_push_updatelink); +#endif + return true; +} + +void trigger_push_findtarget(entity this) +{ + trigger_push_test(this, NULL); +} + +#ifdef SVQC +float trigger_push_send(entity this, entity to, float sf) +{ + WriteHeader(MSG_ENTITY, ENT_CLIENT_TRIGGER_PUSH); + + WriteByte(MSG_ENTITY, this.team); + WriteInt24_t(MSG_ENTITY, this.spawnflags); + WriteByte(MSG_ENTITY, this.active); + WriteCoord(MSG_ENTITY, this.height); + + WriteVector(MSG_ENTITY, this.movedir); + + trigger_common_write(this, true); + + return true; +} + +void trigger_push_updatelink(entity this) +{ + this.SendFlags |= SF_TRIGGER_INIT; +} + +void trigger_push_link(entity this) +{ + trigger_link(this, trigger_push_send); +} + +/* + * ENTITY PARAMETERS: + * + * target: target of jump + * height: the absolute value is the height of the highest point of the jump + * trajectory above the higher one of the player and the target. + * the sign indicates whether the highest point is INSIDE (positive) + * or OUTSIDE (negative) of the jump trajectory. General rule: use + * positive values for targets mounted on the floor, and use negative + * values to target a point on the ceiling. + * movedir: if target is not set, this * speed * 10 is the velocity to be reached. + */ +spawnfunc(trigger_push) +{ + SetMovedir(this); + + trigger_init(this); + + this.active = ACTIVE_ACTIVE; + this.use = trigger_push_use; + settouch(this, trigger_push_touch); + + // normal push setup + if (!this.speed) + this.speed = 1000; + this.movedir = this.movedir * this.speed * 10; + + if (!this.noise) + this.noise = "misc/jumppad.wav"; + precache_sound (this.noise); + + trigger_push_link(this); // link it now + + IL_PUSH(g_jumppads, this); + + // this must be called to spawn the teleport waypoints for bots + InitializeEntity(this, trigger_push_findtarget, INITPRIO_FINDTARGET); +} + + +bool target_push_send(entity this, entity to, float sf) +{ + WriteHeader(MSG_ENTITY, ENT_CLIENT_TARGET_PUSH); + + WriteByte(MSG_ENTITY, this.cnt); + WriteString(MSG_ENTITY, this.targetname); + WriteVector(MSG_ENTITY, this.origin); + + WriteAngle(MSG_ENTITY, this.angles_x); + WriteAngle(MSG_ENTITY, this.angles_y); + WriteAngle(MSG_ENTITY, this.angles_z); + + return true; +} + +void target_push_use(entity this, entity actor, entity trigger) +{ + if(trigger.classname == "trigger_push" || trigger == this) + return; // WTF, why is this a thing + + jumppad_push(this, actor); +} + +void target_push_link(entity this) +{ + BITSET_ASSIGN(this.effects, EF_NODEPTHTEST); + Net_LinkEntity(this, false, 0, target_push_send); + //this.SendFlags |= 1; // update +} + +void target_push_init(entity this) +{ + this.mangle = this.angles; + setorigin(this, this.origin); + target_push_link(this); +} + +void target_push_init2(entity this) +{ + if(this.target && this.target != "") // we have an old style pusher! + { + InitializeEntity(this, trigger_push_findtarget, INITPRIO_FINDTARGET); + this.use = target_push_use; + } + + target_push_init(this); // normal push target behaviour can be combined with a legacy pusher? +} + +spawnfunc(target_push) +{ + target_push_init2(this); +} + +spawnfunc(info_notnull) +{ + target_push_init(this); +} +spawnfunc(target_position) +{ + target_push_init(this); +} + +#elif defined(CSQC) + +NET_HANDLE(ENT_CLIENT_TRIGGER_PUSH, bool isnew) +{ + this.classname = "jumppad"; + int mytm = ReadByte(); + if(mytm) + { + this.team = mytm - 1; + } + this.spawnflags = ReadInt24_t(); + this.active = ReadByte(); + this.height = ReadCoord(); + + this.movedir = ReadVector(); + + trigger_common_read(this, true); + + this.entremove = trigger_remove_generic; + this.solid = SOLID_TRIGGER; + settouch(this, trigger_push_touch); + this.move_time = time; + defer(this, 0.25, trigger_push_findtarget); + + return true; +} + +void target_push_remove(entity this) +{ + // strfree(this.classname); + strfree(this.targetname); +} + +NET_HANDLE(ENT_CLIENT_TARGET_PUSH, bool isnew) +{ + this.classname = "push_target"; + this.cnt = ReadByte(); + this.targetname = strzone(ReadString()); + this.origin = ReadVector(); + + this.angles_x = ReadAngle(); + this.angles_y = ReadAngle(); + this.angles_z = ReadAngle(); + + return = true; + + setorigin(this, this.origin); + + this.drawmask = MASK_NORMAL; + this.entremove = target_push_remove; +} +#endif diff --git a/qcsrc/common/mapobjects/trigger/jumppads.qh b/qcsrc/common/mapobjects/trigger/jumppads.qh new file mode 100644 index 000000000..cd6adec31 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/jumppads.qh @@ -0,0 +1,63 @@ +#pragma once + + +const int PUSH_ONCE = BIT(0); // legacy, deactivate with relay instead +const int PUSH_SILENT = BIT(1); // not used? + +IntrusiveList g_jumppads; +STATIC_INIT(g_jumppads) { g_jumppads = IL_NEW(); } + +.float pushltime; +.float istypefrag; +.float height; + +const int NUM_JUMPPADSUSED = 3; +.float jumppadcount; +.entity jumppadsused[NUM_JUMPPADSUSED]; + +#ifdef SVQC +void SUB_UseTargets(entity this, entity actor, entity trigger); +void trigger_push_use(entity this, entity actor, entity trigger); +bool trigger_push_testorigin(entity tracetest_ent, entity targ, entity jp, vector org); +bool trigger_push_testorigin_for_item(entity tracetest_ent, entity item, vector org); +#endif + +/* + trigger_push_calculatevelocity + + Arguments: + org - origin of the object which is to be pushed + tgt - target entity (can be either a point or a model entity; if it is + the latter, its midpoint is used) + ht - jump height, measured from the higher one of org and tgt's midpoint + pushed_entity - object that is to be pushed + + Returns: velocity for the jump + */ +vector trigger_push_calculatevelocity(vector org, entity tgt, float ht, entity pushed_entity); + +void trigger_push_touch(entity this, entity toucher); + +.vector dest; +bool trigger_push_test(entity this, entity item); +void trigger_push_findtarget(entity this); + +/* + * ENTITY PARAMETERS: + * + * target: target of jump + * height: the absolute value is the height of the highest point of the jump + * trajectory above the higher one of the player and the target. + * the sign indicates whether the highest point is INSIDE (positive) + * or OUTSIDE (negative) of the jump trajectory. General rule: use + * positive values for targets mounted on the floor, and use negative + * values to target a point on the ceiling. + * movedir: if target is not set, this * speed * 10 is the velocity to be reached. + */ +#ifdef SVQC +spawnfunc(trigger_push); + +spawnfunc(target_push); +spawnfunc(info_notnull); +spawnfunc(target_position); +#endif diff --git a/qcsrc/common/mapobjects/trigger/keylock.qc b/qcsrc/common/mapobjects/trigger/keylock.qc new file mode 100644 index 000000000..67db14421 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/keylock.qc @@ -0,0 +1,187 @@ +#include "keylock.qh" +/** + * trigger given targets + */ +void trigger_keylock_trigger(entity this, entity actor, string s) +{ + for(entity t = NULL; (t = find(t, targetname, s)); ) + if(t.use) + t.use(t, actor, this); +} + +/** + * kill killtarget of trigger keylock. + */ +void trigger_keylock_kill(string s) +{ + entity t; + for(t = NULL; (t = find(t, targetname, s)); ) + delete(t); +} + +void trigger_keylock_touch(entity this, entity toucher) +{ + bool key_used = false; + bool started_delay = false; + + // only player may trigger the lock + if(!IS_PLAYER(toucher)) + return; + + // check silver key + if(this.itemkeys) + key_used = item_keys_usekey(this, toucher); + + if(this.itemkeys) + { +#ifdef SVQC + // at least one of the keys is missing + if(key_used) + { + // one or more keys were given, but others are still missing! + play2(toucher, this.noise1); + Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(this.itemkeys)); + toucher.key_door_messagetime = time + 2; + } + else if(toucher.key_door_messagetime <= time) + { + // no keys were given + play2(toucher, this.noise2); + Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(this.itemkeys)); + toucher.key_door_messagetime = time + 2; + } +#endif + + // trigger target2 + if(this.delay <= time || started_delay == true) + if(this.target2) + { + trigger_keylock_trigger(this, toucher, this.target2); + started_delay = true; + this.delay = time + this.wait; + } + } + else + { +#ifdef SVQC + // all keys were given! + play2(toucher, this.noise); + centerprint(toucher, this.message); +#endif + + if(this.target) + trigger_keylock_trigger(this, toucher, this.target); + + if(this.killtarget) + trigger_keylock_kill(this.killtarget); + + delete(this); + } + +} + +REGISTER_NET_LINKED(ENT_CLIENT_KEYLOCK) + +#ifdef SVQC +bool trigger_keylock_send(entity this, entity to, int sf) +{ + WriteHeader(MSG_ENTITY, ENT_CLIENT_KEYLOCK); + + WriteInt24_t(MSG_ENTITY, this.itemkeys); + WriteByte(MSG_ENTITY, this.height); + + trigger_common_write(this, true); + + return true; +} + +void trigger_keylock_link(entity this) +{ + // uncomment to network keylocks + //Net_LinkEntity(this, false, 0, trigger_keylock_send); +} + +/*QUAKED trigger_keylock (.0 .5 .8) ? +Keylock trigger. Must target other entities. +This trigger will trigger target entities when all required keys are provided. +-------- KEYS -------- +itemkeys: A bit field with key IDs that are needed to open this lock. +sounds: 1 to play misc/secret.wav, 2 to play misc/talk.wav, 3 to play misc/trigger1.wav (3 is default) +target: trigger all entities with this targetname when triggered and all keys have been given to it, then remove this trigger +target2: trigger all entities with this targetname when triggered without giving it all the required keys. +killtarget: remove all entities with this targetname when triggered with all the needed keys. +message: print this message to the player who activated the trigger when all needed keys have been given. +message2: print this message to the player who activated the trigger when not all of the needed keys have been given. +noise: sound to play when lock gets unlocked (default: see sounds) +noise1: sound to play when only some of the needed key were used but not all (default: misc/decreasevalue.wav) +noise2: sound to play when a key is missing (default: misc/talk.wav) +wait: prevent triggering again for this amount of time (default: 5) - applies to target2, target3, target4. +---------NOTES---------- +If spawned without any key specified in itemkeys, this trigger will display an error and remove itself. +message2 and noise2 will be resent to the player every 2 seconds while he is in the trigger zone. +*/ +spawnfunc(trigger_keylock) +{ + if(!this.itemkeys) { delete(this); return; } + + // set unlocked message + if(this.message == "") + this.message = "Unlocked!"; + + // set default unlock noise + if(this.noise == "") + { + if(this.sounds == 1) + this.noise = "misc/secret.wav"; + else if(this.sounds == 2) + this.noise = strzone(SND(TALK)); + else //if (this.sounds == 3) { + this.noise = "misc/trigger1.wav"; + } + + // set default use key sound + if(this.noise1 == "") + this.noise1 = "misc/decreasevalue.wav"; + + // set closed sourd + if(this.noise2 == "") + this.noise2 = SND(TALK); + + // delay between triggering message2 and trigger2 + if(!this.wait) { this.wait = 5; } + + // precache sounds + precache_sound(this.noise); + precache_sound(this.noise1); + precache_sound(this.noise2); + + EXACTTRIGGER_INIT; + + settouch(this, trigger_keylock_touch); + + trigger_keylock_link(this); +} +#elif defined(CSQC) +void keylock_remove(entity this) +{ + strfree(this.target); + strfree(this.target2); + strfree(this.target3); + strfree(this.target4); + strfree(this.killtarget); + strfree(this.targetname); +} + +NET_HANDLE(ENT_CLIENT_KEYLOCK, bool isnew) +{ + this.itemkeys = ReadInt24_t(); + this.height = ReadByte(); + + trigger_common_read(this, true); + + return = true; + + this.classname = "trigger_keylock"; + this.entremove = keylock_remove; +} +#endif diff --git a/qcsrc/common/mapobjects/trigger/keylock.qh b/qcsrc/common/mapobjects/trigger/keylock.qh new file mode 100644 index 000000000..904c3fa3d --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/keylock.qh @@ -0,0 +1,10 @@ +#pragma once + +#ifdef CSQC +bool item_keys_usekey(entity l, entity p) +{ + int valid = (l.itemkeys & p.itemkeys); // TODO: itemkeys isn't networked or anything! + l.itemkeys &= ~valid; // only some of the needed keys were given + return valid != 0; +} +#endif diff --git a/qcsrc/common/mapobjects/trigger/magicear.qc b/qcsrc/common/mapobjects/trigger/magicear.qc new file mode 100644 index 000000000..16118cb9d --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/magicear.qc @@ -0,0 +1,200 @@ +#include "magicear.qh" +#ifdef SVQC +float magicear_matched; +float W_Tuba_HasPlayed(entity pl, .entity weaponentity, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo); +string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin) +{ + float domatch, dotrigger, matchstart, l; + string s, msg; + string savemessage; + + magicear_matched = false; + + dotrigger = ((IS_PLAYER(source)) && (!IS_DEAD(source)) && ((ear.radius == 0) || (vdist(source.origin - ear.origin, <=, ear.radius)))); + domatch = ((ear.spawnflags & MAGICEAR_REPLACE_OUTSIDE) || dotrigger); + + if (!domatch) + return msgin; + + if (!msgin) + { + // we are in TUBA mode! + if (!(ear.spawnflags & MAGICEAR_TUBA)) + return msgin; + + for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + .entity weaponentity = weaponentities[slot]; + if(!W_Tuba_HasPlayed(source, weaponentity, ear.message, ear.movedir_x, !(ear.spawnflags & MAGICEAR_TUBA_EXACTPITCH), ear.movedir_y, ear.movedir_z)) + return msgin; + } + + magicear_matched = true; + + if(dotrigger) + { + savemessage = ear.message; + ear.message = string_null; + SUB_UseTargets(ear, source, NULL); + ear.message = savemessage; + } + + if(ear.netname != "") + return ear.netname; + + return msgin; + } + + if(ear.spawnflags & MAGICEAR_TUBA) // ENOTUBA + return msgin; + + if(privatesay) + { + if(ear.spawnflags & MAGICEAR_IGNORE_TELL) + return msgin; + } + else + { + if(!teamsay) + if(ear.spawnflags & MAGICEAR_IGNORE_SAY) + return msgin; + if(teamsay > 0) + if(ear.spawnflags & MAGICEAR_IGNORE_TEAMSAY) + return msgin; + if(teamsay < 0) + if(ear.spawnflags & MAGICEAR_IGNORE_INVALIDTELL) + return msgin; + } + + matchstart = -1; + l = strlen(ear.message); + + if(ear.spawnflags & MAGICEAR_NODECOLORIZE) + msg = msgin; + else + msg = strdecolorize(msgin); + + if(substring(ear.message, 0, 1) == "*") + { + if(substring(ear.message, -1, 1) == "*") + { + // two wildcards + // as we need multi-replacement here... + s = substring(ear.message, 1, -2); + l -= 2; + if(strstrofs(msg, s, 0) >= 0) + matchstart = -2; // we use strreplace on s + } + else + { + // match at start + s = substring(ear.message, 1, -1); + l -= 1; + if(substring(msg, -l, l) == s) + matchstart = strlen(msg) - l; + } + } + else + { + if(substring(ear.message, -1, 1) == "*") + { + // match at end + s = substring(ear.message, 0, -2); + l -= 1; + if(substring(msg, 0, l) == s) + matchstart = 0; + } + else + { + // full match + s = ear.message; + if(msg == ear.message) + matchstart = 0; + } + } + + if(matchstart == -1) // no match + return msgin; + + magicear_matched = true; + + if(dotrigger) + { + savemessage = ear.message; + ear.message = string_null; + SUB_UseTargets(ear, source, NULL); + ear.message = savemessage; + } + + if(ear.spawnflags & MAGICEAR_REPLACE_WHOLE_MESSAGE) + { + return ear.netname; + } + else if(ear.netname != "") + { + if(matchstart < 0) + return strreplace(s, ear.netname, msg); + else + return strcat( + substring(msg, 0, matchstart), + ear.netname, + substring(msg, matchstart + l, -1) + ); + } + else + return msgin; +} + +entity magicears; +string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin) +{ + entity ear; + string msgout; + for(ear = magicears; ear; ear = ear.enemy) + { + msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin); + if(!(ear.spawnflags & MAGICEAR_CONTINUE)) + if(magicear_matched) + return msgout; + msgin = msgout; + } + return msgin; +} + +spawnfunc(trigger_magicear) +{ + this.enemy = magicears; + magicears = this; + + // actually handled in "say" processing + // spawnflags: + // 1 = ignore say + // 2 = ignore teamsay + // 4 = ignore tell + // 8 = ignore tell to unknown player + // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set) + // 32 = perform the replacement even if outside the radius or dead + // 64 = continue replacing/triggering even if this one matched + // 128 = don't decolorize message before matching + // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...) + // 512 = tuba notes must be exact right pitch, no transposing + // message: either + // *pattern* + // or + // *pattern + // or + // pattern* + // or + // pattern + // netname: + // if set, replacement for the matched text + // radius: + // "hearing distance" + // target: + // what to trigger + // movedir: + // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter) + + this.movedir_x -= 1; // map to tuba instrument numbers +} +#endif diff --git a/qcsrc/common/mapobjects/trigger/magicear.qh b/qcsrc/common/mapobjects/trigger/magicear.qh new file mode 100644 index 000000000..4e705868c --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/magicear.qh @@ -0,0 +1,13 @@ +#pragma once + + +const int MAGICEAR_IGNORE_SAY = BIT(0); +const int MAGICEAR_IGNORE_TEAMSAY = BIT(1); +const int MAGICEAR_IGNORE_TELL = BIT(2); +const int MAGICEAR_IGNORE_INVALIDTELL = BIT(3); +const int MAGICEAR_REPLACE_WHOLE_MESSAGE = BIT(4); +const int MAGICEAR_REPLACE_OUTSIDE = BIT(5); +const int MAGICEAR_CONTINUE = BIT(6); +const int MAGICEAR_NODECOLORIZE = BIT(7); +const int MAGICEAR_TUBA = BIT(8); +const int MAGICEAR_TUBA_EXACTPITCH = BIT(9); diff --git a/qcsrc/common/mapobjects/trigger/monoflop.qc b/qcsrc/common/mapobjects/trigger/monoflop.qc new file mode 100644 index 000000000..0c960ba8a --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/monoflop.qc @@ -0,0 +1,49 @@ +#include "monoflop.qh" +#ifdef SVQC +/*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8) +"Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait" +*/ +void monoflop_use(entity this, entity actor, entity trigger) +{ + this.nextthink = time + this.wait; + this.enemy = actor; + if(this.state) + return; + this.state = 1; + SUB_UseTargets(this, actor, trigger); +} +void monoflop_fixed_use(entity this, entity actor, entity trigger) +{ + if(this.state) + return; + this.nextthink = time + this.wait; + this.state = 1; + this.enemy = actor; + SUB_UseTargets(this, actor, trigger); +} + +void monoflop_think(entity this) +{ + this.state = 0; + SUB_UseTargets(this, this.enemy, NULL); +} + +void monoflop_reset(entity this) +{ + this.state = 0; + this.nextthink = 0; +} + +spawnfunc(trigger_monoflop) +{ + if(!this.wait) + this.wait = 1; + if(this.spawnflags & MONOFLOP_FIXED) + this.use = monoflop_fixed_use; + else + this.use = monoflop_use; + setthink(this, monoflop_think); + this.state = 0; + this.reset = monoflop_reset; +} +#endif diff --git a/qcsrc/common/mapobjects/trigger/monoflop.qh b/qcsrc/common/mapobjects/trigger/monoflop.qh new file mode 100644 index 000000000..c64dffdee --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/monoflop.qh @@ -0,0 +1,4 @@ +#pragma once + + +const int MONOFLOP_FIXED = BIT(0); diff --git a/qcsrc/common/mapobjects/trigger/multi.qc b/qcsrc/common/mapobjects/trigger/multi.qc new file mode 100644 index 000000000..accfbe8ac --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/multi.qc @@ -0,0 +1,224 @@ +#include "multi.qh" +// NOTE: also contains trigger_once at bottom + +#ifdef SVQC +// the wait time has passed, so set back up for another activation +void multi_wait(entity this) +{ + if (this.max_health) + { + this.health = this.max_health; + this.takedamage = DAMAGE_YES; + this.solid = SOLID_BBOX; + } +} + + +// the trigger was just touched/killed/used +// this.enemy should be set to the activator so it can be held through a delay +// so wait for the delay time before firing +void multi_trigger(entity this) +{ + if (this.nextthink > time) + { + return; // allready been triggered + } + + if(this.spawnflags & ONLY_PLAYERS && !IS_PLAYER(this.enemy)) + { + return; // only players + } + + // TODO: restructure this so that trigger_secret is more independent + if (this.classname == "trigger_secret") + { + if (!IS_PLAYER(this.enemy)) + return; + found_secrets = found_secrets + 1; + WriteByte (MSG_ALL, SVC_FOUNDSECRET); + } + + if (this.noise) + { + _sound (this.enemy, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM); + } + + // don't trigger again until reset + this.takedamage = DAMAGE_NO; + + SUB_UseTargets(this, this.enemy, this.goalentity); + + if (this.wait > 0) + { + setthink(this, multi_wait); + this.nextthink = time + this.wait; + } + else if (this.wait == 0) + { + multi_wait(this); // waiting finished + } + else + { // we can't just delete(this) here, because this is a touch function + // called while C code is looping through area links... + settouch(this, func_null); + } +} + +void multi_use(entity this, entity actor, entity trigger) +{ + this.goalentity = trigger; + this.enemy = actor; + multi_trigger(this); +} + +void multi_touch(entity this, entity toucher) +{ + if(!(this.spawnflags & ALL_ENTITIES) && !toucher.iscreature) + { + return; + } + + if(this.team) + { + if(((this.spawnflags & INVERT_TEAMS) == 0) == (this.team != toucher.team)) + { + return; + } + } + + // if the trigger has an angles field, check player's facing direction + if (this.movedir != '0 0 0') + { + makevectors (toucher.angles); + if (v_forward * this.movedir < 0) + return; // not facing the right way + } + + // if the trigger has pressed keys, check that the player is pressing those keys + if(this.pressedkeys && IS_PLAYER(toucher)) // only for players + { + if(!(CS(toucher).pressedkeys & this.pressedkeys)) + { + return; + } + } + + EXACTTRIGGER_TOUCH(this, toucher); + + this.enemy = toucher; + this.goalentity = toucher; + multi_trigger(this); +} + +void multi_eventdamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) +{ + if(!this.takedamage) + return; + if(this.spawnflags & NOSPLASH) + if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH)) + return; + if(this.team) + if(((this.spawnflags & INVERT_TEAMS) == 0) == (this.team != attacker.team)) + return; + this.health = this.health - damage; + if (this.health <= 0) + { + this.enemy = attacker; + this.goalentity = inflictor; + multi_trigger(this); + } +} + +void multi_reset(entity this) +{ + if ( !(this.spawnflags & SPAWNFLAG_NOTOUCH) ) + settouch(this, multi_touch); + if (this.max_health) + { + this.health = this.max_health; + this.takedamage = DAMAGE_YES; + this.solid = SOLID_BBOX; + } + setthink(this, func_null); + this.nextthink = 0; + this.team = this.team_saved; +} + +/*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch +Variable sized repeatable trigger. Must be targeted at one or more entities. If "health" is set, the trigger must be killed to activate each time. +If "delay" is set, the trigger waits some time after activating before firing. +"wait" : Seconds between triggerings. (.2 default) +If notouch is set, the trigger is only fired by other entities, not by touching. +NOTOUCH has been obsoleted by spawnfunc_trigger_relay! +sounds +1) secret +2) beep beep +3) large switch +4) +set "message" to text string +*/ +spawnfunc(trigger_multiple) +{ + this.reset = multi_reset; + if (this.sounds == 1) + this.noise = "misc/secret.wav"; + else if (this.sounds == 2) + this.noise = strzone(SND(TALK)); + else if (this.sounds == 3) + this.noise = "misc/trigger1.wav"; + + if(this.noise) + precache_sound(this.noise); + + if (!this.wait) + this.wait = 0.2; + else if(this.wait < -1) + this.wait = 0; + this.use = multi_use; + + EXACTTRIGGER_INIT; + + this.team_saved = this.team; + IL_PUSH(g_saved_team, this); + + if (this.health) + { + if (this.spawnflags & SPAWNFLAG_NOTOUCH) + objerror (this, "health and notouch don't make sense\n"); + this.canteamdamage = true; + this.max_health = this.health; + this.event_damage = multi_eventdamage; + this.takedamage = DAMAGE_YES; + this.solid = SOLID_BBOX; + setorigin(this, this.origin); // make sure it links into the world + } + else + { + if ( !(this.spawnflags & SPAWNFLAG_NOTOUCH) ) + { + settouch(this, multi_touch); + setorigin(this, this.origin); // make sure it links into the world + } + } +} + + +/*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch +Variable sized trigger. Triggers once, then removes itself. You must set the key "target" to the name of another object in the level that has a matching +"targetname". If "health" is set, the trigger must be killed to activate. +If notouch is set, the trigger is only fired by other entities, not by touching. +if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired. +if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0. +sounds +1) secret +2) beep beep +3) large switch +4) +set "message" to text string +*/ +spawnfunc(trigger_once) +{ + this.wait = -1; + spawnfunc_trigger_multiple(this); +} +#endif diff --git a/qcsrc/common/mapobjects/trigger/multi.qh b/qcsrc/common/mapobjects/trigger/multi.qh new file mode 100644 index 000000000..43358c274 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/multi.qh @@ -0,0 +1,8 @@ +#pragma once + +#ifdef SVQC +void multi_trigger(entity this); +void multi_reset(entity this); + +spawnfunc(trigger_once); +#endif diff --git a/qcsrc/common/mapobjects/trigger/multivibrator.qc b/qcsrc/common/mapobjects/trigger/multivibrator.qc new file mode 100644 index 000000000..932fda13c --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/multivibrator.qc @@ -0,0 +1,78 @@ +#include "multivibrator.qh" +#ifdef SVQC +void multivibrator_send(entity this) +{ + float newstate; + float cyclestart; + + cyclestart = floor((time + this.phase) / (this.wait + this.respawntime)) * (this.wait + this.respawntime) - this.phase; + + newstate = (time < cyclestart + this.wait); + + if(this.state != newstate) + SUB_UseTargets(this, this, NULL); + this.state = newstate; + + if(this.state) + this.nextthink = cyclestart + this.wait + 0.01; + else + this.nextthink = cyclestart + this.wait + this.respawntime + 0.01; +} + +void multivibrator_send_think(entity this) +{ + multivibrator_send(this); +} + +void multivibrator_toggle(entity this, entity actor, entity trigger) +{ + if(this.nextthink == 0) + { + multivibrator_send(this); + } + else + { + if(this.state) + { + SUB_UseTargets(this, actor, trigger); + this.state = 0; + } + this.nextthink = 0; + } +} + +void multivibrator_reset(entity this) +{ + if(!(this.spawnflags & START_ENABLED)) + this.nextthink = 0; // wait for a trigger event + else + this.nextthink = max(1, time); +} + +/*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED +"Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off. +-------- KEYS -------- +target: trigger all entities with this targetname when it goes off +targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state +phase: offset of the timing +wait: "on" cycle time (default: 1) +respawntime: "off" cycle time (default: same as wait) +-------- SPAWNFLAGS -------- +START_ENABLED: assume it is already turned on (when targeted) +*/ +spawnfunc(trigger_multivibrator) +{ + if(!this.wait) + this.wait = 1; + if(!this.respawntime) + this.respawntime = this.wait; + + this.state = 0; + this.use = multivibrator_toggle; + setthink(this, multivibrator_send_think); + this.nextthink = max(1, time); + + IFTARGETED + multivibrator_reset(this); +} +#endif diff --git a/qcsrc/common/mapobjects/trigger/multivibrator.qh b/qcsrc/common/mapobjects/trigger/multivibrator.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/multivibrator.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/trigger/relay.qc b/qcsrc/common/mapobjects/trigger/relay.qc new file mode 100644 index 000000000..f99d364ae --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/relay.qc @@ -0,0 +1,26 @@ +#include "relay.qh" +#ifdef SVQC + +void relay_use(entity this, entity actor, entity trigger) +{ + if(this.active != ACTIVE_ACTIVE) + return; + + SUB_UseTargets(this, actor, trigger); +} + +/*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) +This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages. +*/ +spawnfunc(trigger_relay) +{ + this.active = ACTIVE_ACTIVE; + this.use = relay_use; + this.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully +} + +spawnfunc(target_relay) +{ + spawnfunc_trigger_relay(this); +} +#endif diff --git a/qcsrc/common/mapobjects/trigger/relay.qh b/qcsrc/common/mapobjects/trigger/relay.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/relay.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/trigger/relay_activators.qc b/qcsrc/common/mapobjects/trigger/relay_activators.qc new file mode 100644 index 000000000..18c2a40d0 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/relay_activators.qc @@ -0,0 +1,34 @@ +#include "relay_activators.qh" +#ifdef SVQC +void relay_activators_use(entity this, entity actor, entity trigger) +{ + for(entity trg = NULL; (trg = find(trg, targetname, this.target)); ) + { + if (trg.setactive) + trg.setactive(trg, this.cnt); + else + { + //bprint("Not using setactive\n"); + generic_setactive(trg, this.cnt); + } + } +} + +spawnfunc(relay_activate) +{ + this.cnt = ACTIVE_ACTIVE; + this.use = relay_activators_use; +} + +spawnfunc(relay_deactivate) +{ + this.cnt = ACTIVE_NOT; + this.use = relay_activators_use; +} + +spawnfunc(relay_activatetoggle) +{ + this.cnt = ACTIVE_TOGGLE; + this.use = relay_activators_use; +} +#endif diff --git a/qcsrc/common/mapobjects/trigger/relay_activators.qh b/qcsrc/common/mapobjects/trigger/relay_activators.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/relay_activators.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/trigger/relay_if.qc b/qcsrc/common/mapobjects/trigger/relay_if.qc new file mode 100644 index 000000000..9adcd666e --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/relay_if.qc @@ -0,0 +1,20 @@ +#include "relay_if.qh" +#ifdef SVQC +void trigger_relay_if_use(entity this, entity actor, entity trigger) +{ + int n = this.count; + + // TODO make this generic AND faster than nextent()ing through all, if somehow possible + n = (cvar_string(this.netname) == cvar_string(this.message)); + if(this.spawnflags & RELAYIF_NEGATE) + n = !n; + + if(n) + SUB_UseTargets(this, actor, trigger); +} + +spawnfunc(trigger_relay_if) +{ + this.use = trigger_relay_if_use; +} +#endif diff --git a/qcsrc/common/mapobjects/trigger/relay_if.qh b/qcsrc/common/mapobjects/trigger/relay_if.qh new file mode 100644 index 000000000..6f37aa71d --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/relay_if.qh @@ -0,0 +1,4 @@ +#pragma once + + +const int RELAYIF_NEGATE = BIT(0); diff --git a/qcsrc/common/mapobjects/trigger/relay_teamcheck.qc b/qcsrc/common/mapobjects/trigger/relay_teamcheck.qc new file mode 100644 index 000000000..bf03b1542 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/relay_teamcheck.qc @@ -0,0 +1,37 @@ +#include "relay_teamcheck.qh" +#ifdef SVQC +void trigger_relay_teamcheck_use(entity this, entity actor, entity trigger) +{ + if(actor.team) + { + if(this.spawnflags & RELAYTEAMCHECK_INVERT) + { + if(DIFF_TEAM(actor, this)) + SUB_UseTargets(this, actor, trigger); + } + else + { + if(SAME_TEAM(actor, this)) + SUB_UseTargets(this, actor, trigger); + } + } + else + { + if(this.spawnflags & RELAYTEAMCHECK_NOTEAM) + SUB_UseTargets(this, actor, trigger); + } +} + +void trigger_relay_teamcheck_reset(entity this) +{ + this.team = this.team_saved; +} + +spawnfunc(trigger_relay_teamcheck) +{ + this.team_saved = this.team; + IL_PUSH(g_saved_team, this); + this.use = trigger_relay_teamcheck_use; + this.reset = trigger_relay_teamcheck_reset; +} +#endif diff --git a/qcsrc/common/mapobjects/trigger/relay_teamcheck.qh b/qcsrc/common/mapobjects/trigger/relay_teamcheck.qh new file mode 100644 index 000000000..602d25356 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/relay_teamcheck.qh @@ -0,0 +1,5 @@ +#pragma once + + +const int RELAYTEAMCHECK_NOTEAM = BIT(0); +const int RELAYTEAMCHECK_INVERT = BIT(1); diff --git a/qcsrc/common/mapobjects/trigger/secret.qc b/qcsrc/common/mapobjects/trigger/secret.qc new file mode 100644 index 000000000..9377332e2 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/secret.qc @@ -0,0 +1,90 @@ +#include "secret.qh" +#if defined(CSQC) +#elif defined(MENUQC) +#elif defined(SVQC) + #include + #include +#endif + +#ifdef SVQC + +void secrets_setstatus(entity this) +{ + // TODO: use global stats! + STAT(SECRETS_TOTAL, this) = secrets_total; + STAT(SECRETS_FOUND, this) = secrets_found; +} + +/** + * A secret has been found (maybe :P) + */ +void trigger_secret_touch(entity this, entity toucher) +{ + // only a player can trigger this + if (!IS_PLAYER(toucher)) + return; + + // update secrets found counter + secrets_found += 1; + //print("Secret found: ", ftos(secret_counter.cnt), "/"); + //print(ftos(secret_counter.count), "\n"); + + // centerprint message (multi_touch() doesn't always call centerprint()) + centerprint(toucher, this.message); + this.message = ""; + + // handle normal trigger features + multi_touch(this, toucher); + // we can't just delete(this) here, because this is a touch function + // called while C code is looping through area links... + //delete(this); +} + +/*QUAKED trigger_secret (.5 .5 .5) ? +Variable sized secret trigger. Can be targeted at one or more entities. +Basically, it's a trigger_once (with restrictions, see notes) that additionally updates the number of secrets found. +-------- KEYS -------- +sounds: 1 to play misc/secret.wav, 2 to play misc/talk.wav, 3 to play misc/trigger1.wav (default: 1) +noise: path to sound file, if you want to play something else +target: trigger all entities with this targetname when triggered +message: print this message to the player who activated the trigger instead of the standard 'You found a secret!' +killtarget: remove all entities with this targetname when triggered +-------- NOTES -------- +You should create a common/trigger textured brush covering the entrance to a secret room/area. +Trigger secret can only be trigger by a player's touch and can not be a target itself. +*/ +spawnfunc(trigger_secret) +{ + // FIXME: should it be disabled in most modes? + + // update secrets count + secrets_total += 1; + + // add default message + if (this.message == "") + this.message = "You found a secret!"; + + // set default sound + if (this.noise == "") + if (!this.sounds) + this.sounds = 1; // misc/secret.wav + + // this entity can't be a target itself!!!! + this.targetname = ""; + + // you can't just shoot a room to find it, can you? + this.health = 0; + + // a secret can not be delayed + this.delay = 0; + + // convert this trigger to trigger_once + //this.classname = "trigger_once"; + spawnfunc_trigger_once(this); + + // take over the touch() function, so we can mark secret as found + settouch(this, trigger_secret_touch); + // ignore triggering; + this.use = func_null; +} +#endif diff --git a/qcsrc/common/mapobjects/trigger/secret.qh b/qcsrc/common/mapobjects/trigger/secret.qh new file mode 100644 index 000000000..fcc55c395 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/secret.qh @@ -0,0 +1,19 @@ +#pragma once +#ifdef SVQC + +/** + * Total number of secrets on the map. + */ +float secrets_total; + +/** + * Total numbe of secrets found on the map. + */ +float secrets_found; + + +/** + * update secrets status. + */ +void secrets_setstatus(entity this); +#endif diff --git a/qcsrc/common/mapobjects/trigger/swamp.qc b/qcsrc/common/mapobjects/trigger/swamp.qc new file mode 100644 index 000000000..058e41ca2 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/swamp.qc @@ -0,0 +1,157 @@ +#include "swamp.qh" +#if defined(CSQC) +#elif defined(MENUQC) +#elif defined(SVQC) + #include + #include + #include + #include +#endif + +/* +* t_swamp.c +* Adds spawnfunc_trigger_swamp and suppoart routines for xonotic 1.2.1+ +* Author tZork (Jakob MG) +* jakob@games43.se +* 2005 11 29 +*/ + +.float swamp_interval; //Hurt players in swamp with this interval +.float swamp_slowdown; //Players in swamp get slowd down by this mutch 0-1 is slowdown 1-~ is speedup (!?) +.entity swampslug; + +#ifdef SVQC +spawnfunc(trigger_swamp); +#endif +void swamp_touch(entity this, entity toucher); +void swampslug_think(entity this); + + +/* +* Uses a entity calld swampslug to handle players in the swamp +* It works like this: When the plyer enters teh swamp the spawnfunc_trigger_swamp +* attaches a new "swampslug" to the player. As long as the plyer is inside +* the swamp the swamp gives the slug new health. But the slug slowly kills itself +* so when the player goes outside the swamp, it dies and releases the player from the +* swamps curses (dmg/slowdown) +* +* I do it this way becuz there is no "untouch" event. +*/ +void swampslug_think(entity this) +{ + //Slowly kill the slug + this.health = this.health - 1; + + //Slug dead? then remove curses. + if(this.health <= 0) + { + this.owner.in_swamp = 0; + delete(this); + //centerprint(this.owner,"Killing slug...\n"); + return; + } + + // Slug still alive, so we are still in the swamp + // Or we have exited it very recently. + // Do the damage and renew the timer. +#ifdef SVQC + Damage (this.owner, this, this, this.dmg, DEATH_SWAMP.m_id, DMG_NOWEP, this.owner.origin, '0 0 0'); +#endif + + this.nextthink = time + this.swamp_interval; +} + +void swamp_touch(entity this, entity toucher) +{ + // If whatever thats touching the swamp is not a player + // or if its a dead player, just dont care abt it. + if(!IS_PLAYER(toucher) || IS_DEAD(toucher)) + return; + + EXACTTRIGGER_TOUCH(this, toucher); + + // Chech if player alredy got a swampslug. + if(toucher.in_swamp != 1) + { + // If not attach one. + //centerprint(toucher,"Entering swamp!\n"); + toucher.swampslug = spawn(); + toucher.swampslug.health = 2; + setthink(toucher.swampslug, swampslug_think); + toucher.swampslug.nextthink = time; + toucher.swampslug.owner = toucher; + toucher.swampslug.dmg = this.dmg; + toucher.swampslug.swamp_interval = this.swamp_interval; + toucher.swamp_slowdown = this.swamp_slowdown; + toucher.in_swamp = 1; + return; + } + + //toucher.in_swamp = 1; + + //Revitalize players swampslug + toucher.swampslug.health = 2; +} + +REGISTER_NET_LINKED(ENT_CLIENT_SWAMP) + +#ifdef SVQC +float swamp_send(entity this, entity to, float sf) +{ + WriteHeader(MSG_ENTITY, ENT_CLIENT_SWAMP); + + WriteByte(MSG_ENTITY, this.dmg); // can probably get away with using a single byte here + WriteByte(MSG_ENTITY, this.swamp_slowdown); + WriteByte(MSG_ENTITY, this.swamp_interval); + + trigger_common_write(this, false); + + return true; +} + +void swamp_link(entity this) +{ + trigger_link(this, swamp_send); +} + +/*QUAKED spawnfunc_trigger_swamp (.5 .5 .5) ? +Players gettin into the swamp will +get slowd down and damaged +*/ +spawnfunc(trigger_swamp) +{ + // Init stuff + trigger_init(this); + settouch(this, swamp_touch); + + // Setup default keys, if missing + if(this.dmg <= 0) + this.dmg = 5; + if(this.swamp_interval <= 0) + this.swamp_interval = 1; + if(this.swamp_slowdown <= 0) + this.swamp_slowdown = 0.5; + + swamp_link(this); +} + +#elif defined(CSQC) + +NET_HANDLE(ENT_CLIENT_SWAMP, bool isnew) +{ + this.dmg = ReadByte(); + this.swamp_slowdown = ReadByte(); + this.swamp_interval = ReadByte(); + + trigger_common_read(this, false); + + return = true; + + this.classname = "trigger_swamp"; + this.solid = SOLID_TRIGGER; + settouch(this, swamp_touch); + this.drawmask = MASK_NORMAL; + this.move_time = time; + this.entremove = trigger_remove_generic; +} +#endif diff --git a/qcsrc/common/mapobjects/trigger/swamp.qh b/qcsrc/common/mapobjects/trigger/swamp.qh new file mode 100644 index 000000000..f4df98378 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/swamp.qh @@ -0,0 +1,8 @@ +#pragma once + +.float swamp_interval; //Hurt players in swamp with this interval +.float swamp_slowdown; //Players in swamp get slowd down by this mutch 0-1 is slowdown 1-~ is speedup (!?) +.entity swampslug; + +.float in_swamp; // bool +.entity swampslug; // Uses this to release from swamp ("untouch" fix) diff --git a/qcsrc/common/mapobjects/trigger/teleport.qc b/qcsrc/common/mapobjects/trigger/teleport.qc new file mode 100644 index 000000000..825dd01dd --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/teleport.qc @@ -0,0 +1,183 @@ +#include "teleport.qh" +REGISTER_NET_LINKED(ENT_CLIENT_TRIGGER_TELEPORT) + +#ifdef SVQC +void trigger_teleport_use(entity this, entity actor, entity trigger) +{ + if(teamplay) + this.team = actor.team; +#ifdef SVQC + this.SendFlags |= SF_TRIGGER_UPDATE; +#endif +} +#endif + +bool Teleport_Active(entity this, entity player) +{ + if (this.active != ACTIVE_ACTIVE) + return false; + +#ifdef SVQC + if (!player.teleportable) + return false; + + if(player.vehicle) + if(!player.vehicle.teleportable) + return false; + + if(IS_TURRET(player)) + return false; +#elif defined(CSQC) + if(!IS_PLAYER(player)) + return false; +#endif + + if(IS_DEAD(player)) + return false; + + if(this.team) + if(((this.spawnflags & INVERT_TEAMS) == 0) == (DIFF_TEAM(this, player))) + return false; + + return true; +} + +void Teleport_Touch(entity this, entity toucher) +{ + entity player = toucher; + + if(!Teleport_Active(this, player)) + return; + + EXACTTRIGGER_TOUCH(this, player); + +#ifdef SVQC + if(IS_PLAYER(player)) + RemoveGrapplingHooks(player); +#endif + + entity e; + e = Simple_TeleportPlayer(this, player); + +#ifdef SVQC + string s = this.target; this.target = string_null; + SUB_UseTargets(this, player, player); // TODO: should we be using toucher for trigger too? + if (!this.target) this.target = s; + + SUB_UseTargets(e, player, player); +#endif +} + +#ifdef SVQC +void target_teleport_use(entity this, entity actor, entity trigger) +{ + entity player = actor; + + if(!Teleport_Active(this, player)) + return; + + if(IS_PLAYER(player)) + RemoveGrapplingHooks(player); + + entity e = Simple_TeleportPlayer(this, player); + + string s = this.target; this.target = string_null; + SUB_UseTargets(this, player, player); // TODO: should we be using toucher for trigger too? + if (!this.target) + { + this.target = s; + } + + SUB_UseTargets(e, player, player); +} +#endif + +#ifdef SVQC +float trigger_teleport_send(entity this, entity to, float sf) +{ + WriteHeader(MSG_ENTITY, ENT_CLIENT_TRIGGER_TELEPORT); + + WriteByte(MSG_ENTITY, this.team); + WriteInt24_t(MSG_ENTITY, this.spawnflags); + WriteByte(MSG_ENTITY, this.active); + WriteCoord(MSG_ENTITY, this.speed); + + trigger_common_write(this, true); + + return true; +} + +void trigger_teleport_link(entity this) +{ + //trigger_link(this, trigger_teleport_send); +} + +spawnfunc(trigger_teleport) +{ + this.angles = '0 0 0'; + + this.active = ACTIVE_ACTIVE; + //trigger_init(this); // only for predicted triggers? + EXACTTRIGGER_INIT; + this.use = trigger_teleport_use; + + if(this.noise != "") + FOREACH_WORD(this.noise, true, precache_sound(it)); + + // this must be called to spawn the teleport waypoints for bots + InitializeEntity(this, teleport_findtarget, INITPRIO_FINDTARGET); + + if (this.target == "") + { + objerror (this, "Teleporter with no target"); + return; + } + + IL_PUSH(g_teleporters, this); +} + +spawnfunc(target_teleporter) +{ + if(this.target == "") + { + // actually a destination! + spawnfunc_info_teleport_destination(this); + return; + } + + this.active = ACTIVE_ACTIVE; + + this.use = target_teleport_use; + + if(this.noise != "") + FOREACH_WORD(this.noise, true, precache_sound(it)); + + InitializeEntity(this, teleport_findtarget, INITPRIO_FINDTARGET); +} +#elif defined(CSQC) +NET_HANDLE(ENT_CLIENT_TRIGGER_TELEPORT, bool isnew) +{ + this.classname = "trigger_teleport"; + if(isnew) + IL_PUSH(g_teleporters, this); + int mytm = ReadByte(); + if(mytm) + { + this.team = mytm - 1; + } + this.spawnflags = ReadInt24_t(); + this.active = ReadByte(); + this.speed = ReadCoord(); + + trigger_common_read(this, true); + + this.entremove = trigger_remove_generic; + this.solid = SOLID_TRIGGER; + //settouch(this, trigger_push_touch); + this.move_time = time; + defer(this, 0.25, teleport_findtarget); + + return true; +} + +#endif diff --git a/qcsrc/common/mapobjects/trigger/teleport.qh b/qcsrc/common/mapobjects/trigger/teleport.qh new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/teleport.qh @@ -0,0 +1 @@ +#pragma once diff --git a/qcsrc/common/mapobjects/trigger/viewloc.qc b/qcsrc/common/mapobjects/trigger/viewloc.qc new file mode 100644 index 000000000..ba5dcbe44 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/viewloc.qc @@ -0,0 +1,213 @@ +#include "viewloc.qh" +#if defined(CSQC) +#elif defined(MENUQC) +#elif defined(SVQC) + #include + #include +#endif + +REGISTER_NET_LINKED(ENT_CLIENT_VIEWLOC) +REGISTER_NET_LINKED(ENT_CLIENT_VIEWLOC_TRIGGER) + +#ifdef SVQC + +void viewloc_think(entity this) +{ + // we abuse this method, rather than using normal .touch, because touch isn't reliable with multiple clients inside the same trigger, and can't "untouch" entities + + // set myself as current viewloc where possible +#if 1 + FOREACH_CLIENT(IS_PLAYER(it) && it.viewloc == this, + { + it.viewloc = NULL; + }); +#else + entity e; + for(e = NULL; (e = findentity(e, viewloc, this)); ) + e.viewloc = NULL; +#endif + +#if 1 + FOREACH_CLIENT(!it.viewloc && IS_PLAYER(it), + { + vector emin = it.absmin; + vector emax = it.absmax; + if(this.solid == SOLID_BSP) + { + emin -= '1 1 1'; + emax += '1 1 1'; + } + if(boxesoverlap(emin, emax, this.absmin, this.absmax)) // quick + { + if(WarpZoneLib_BoxTouchesBrush(emin, emax, this, it)) // accurate + it.viewloc = this; + } + }); +#else + + for(e = findradius((this.absmin + this.absmax) * 0.5, vlen(this.absmax - this.absmin) * 0.5 + 1); e; e = e.chain) + if(!e.viewloc) + if(IS_PLAYER(e)) // should we support non-player entities with this? + //if(!IS_DEAD(e)) // death view is handled separately, we can't override this just yet + { + vector emin = e.absmin; + vector emax = e.absmax; + if(this.solid == SOLID_BSP) + { + emin -= '1 1 1'; + emax += '1 1 1'; + } + if(boxesoverlap(emin, emax, this.absmin, this.absmax)) // quick + if(WarpZoneLib_BoxTouchesBrush(emin, emax, this, e)) // accurate + e.viewloc = this; + } +#endif + + this.nextthink = time; +} + +bool trigger_viewloc_send(entity this, entity to, int sf) +{ + // CSQC doesn't need to know our origin (yet), as we're only available for referencing + WriteHeader(MSG_ENTITY, ENT_CLIENT_VIEWLOC_TRIGGER); + + WriteByte(MSG_ENTITY, this.spawnflags); + + WriteEntity(MSG_ENTITY, this.enemy); + WriteEntity(MSG_ENTITY, this.goalentity); + + WriteVector(MSG_ENTITY, this.origin); + + return true; +} + +void viewloc_init(entity this) +{ + entity e; + for(e = NULL; (e = find(e, targetname, this.target)); ) + if(e.classname == "target_viewlocation_start") + { + this.enemy = e; + break; + } + for(e = NULL; (e = find(e, targetname, this.target2)); ) + if(e.classname == "target_viewlocation_end") + { + this.goalentity = e; + break; + } + + if(!this.enemy) { LOG_INFO("^1FAIL!"); delete(this); return; } + + if(!this.goalentity) + this.goalentity = this.enemy; // make them match so CSQC knows what to do + + Net_LinkEntity(this, false, 0, trigger_viewloc_send); + + setthink(this, viewloc_think); + this.nextthink = time; +} + +spawnfunc(trigger_viewlocation) +{ + // we won't check target2 here yet, as it may not even need to exist + if(this.target == "") { LOG_INFO("^1FAIL!"); delete(this); return; } + + EXACTTRIGGER_INIT; + InitializeEntity(this, viewloc_init, INITPRIO_FINDTARGET); +} + +bool viewloc_send(entity this, entity to, int sf) +{ + WriteHeader(MSG_ENTITY, ENT_CLIENT_VIEWLOC); + + WriteByte(MSG_ENTITY, this.cnt); + + WriteVector(MSG_ENTITY, this.origin); + + WriteAngle(MSG_ENTITY, this.angles_x); + WriteAngle(MSG_ENTITY, this.angles_y); + WriteAngle(MSG_ENTITY, this.angles_z); + + return true; +} + +.float angle; +void viewloc_link(entity this) +{ + if(this.angle) + this.angles_y = this.angle; + Net_LinkEntity(this, false, 0, viewloc_send); +} + +spawnfunc(target_viewlocation_start) +{ + this.classname = "target_viewlocation_start"; + this.cnt = 1; + viewloc_link(this); +} +spawnfunc(target_viewlocation_end) +{ + this.classname = "target_viewlocation_end"; + this.cnt = 2; + viewloc_link(this); +} + +// compatibility +spawnfunc(target_viewlocation) +{ + spawnfunc_target_viewlocation_start(this); +} + +#elif defined(CSQC) + +void trigger_viewloc_updatelink(entity this) +{ + this.enemy = findfloat(NULL, entnum, this.cnt); + this.goalentity = findfloat(NULL, entnum, this.count); +} + +NET_HANDLE(ENT_CLIENT_VIEWLOC_TRIGGER, bool isnew) +{ + this.spawnflags = ReadByte(); + + float point1 = ReadShort(); + float point2 = ReadShort(); + + this.enemy = findfloat(NULL, entnum, point1); + this.goalentity = findfloat(NULL, entnum, point2); + + this.origin = ReadVector(); + + return = true; + + setorigin(this, this.origin); + + this.cnt = point1; + this.count = point2; + + setthink(this, trigger_viewloc_updatelink); + this.nextthink = time + 1; // we need to delay this or else + + this.classname = "trigger_viewlocation"; + this.drawmask = MASK_NORMAL; // not so concerned, but better keep it alive +} + +NET_HANDLE(ENT_CLIENT_VIEWLOC, bool isnew) +{ + this.cnt = ReadByte(); + + this.origin = ReadVector(); + setorigin(this, this.origin); + + this.movedir_x = ReadAngle(); + this.movedir_y = ReadAngle(); + this.movedir_z = ReadAngle(); + + return = true; + + this.classname = ((this.cnt == 2) ? "target_viewlocation_end" : "target_viewlocation_start"); + this.drawmask = MASK_NORMAL; // don't cull it +} + +#endif diff --git a/qcsrc/common/mapobjects/trigger/viewloc.qh b/qcsrc/common/mapobjects/trigger/viewloc.qh new file mode 100644 index 000000000..3c393afd3 --- /dev/null +++ b/qcsrc/common/mapobjects/trigger/viewloc.qh @@ -0,0 +1,14 @@ +#pragma once + + +const int VIEWLOC_NOSIDESCROLL = BIT(0); // NOTE: currently unimplemented +const int VIEWLOC_FREEAIM = BIT(1); +const int VIEWLOC_FREEMOVE = BIT(2); + +.entity viewloc; + +#ifdef CSQC +.entity goalentity; +.entity enemy; +.vector movedir; +#endif diff --git a/qcsrc/common/mapobjects/triggers.qc b/qcsrc/common/mapobjects/triggers.qc new file mode 100644 index 000000000..6968a6556 --- /dev/null +++ b/qcsrc/common/mapobjects/triggers.qc @@ -0,0 +1,353 @@ +#include "triggers.qh" +#ifdef SVQC + #include +#endif + +void SUB_DontUseTargets(entity this, entity actor, entity trigger) { } + +void SUB_UseTargets(entity this, entity actor, entity trigger); + +void DelayThink(entity this) +{ + SUB_UseTargets (this, this.enemy, NULL); + delete(this); +} + +void FixSize(entity e) +{ + e.mins_x = rint(e.mins_x); + e.mins_y = rint(e.mins_y); + e.mins_z = rint(e.mins_z); + + e.maxs_x = rint(e.maxs_x); + e.maxs_y = rint(e.maxs_y); + e.maxs_z = rint(e.maxs_z); +} + +#ifdef SVQC +void generic_setactive(entity this, int act) +{ + if(act == ACTIVE_TOGGLE) + { + if(this.active == ACTIVE_ACTIVE) + { + this.active = ACTIVE_NOT; + } + else + { + this.active = ACTIVE_ACTIVE; + } + } + else + { + this.active = act; + } +} + +void generic_netlinked_setactive(entity this, int act) +{ + int old_status = this.active; + generic_setactive(this, act); + + if (this.active != old_status) + { + this.SendFlags |= SF_TRIGGER_UPDATE; + } +} + +void generic_netlinked_reset(entity this) +{ + IFTARGETED + { + if(this.spawnflags & START_ENABLED) + { + this.active = ACTIVE_ACTIVE; + } + else + { + this.active = ACTIVE_NOT; + } + } + else + { + this.active = ACTIVE_ACTIVE; + } + + this.SendFlags |= SF_TRIGGER_UPDATE; +} + +// Compatibility with old maps +void generic_netlinked_legacy_use(entity this, entity actor, entity trigger) +{ + LOG_WARNF("Entity %s was (de)activated by a trigger, please update map to use relays", this.targetname); + this.setactive(this, ACTIVE_TOGGLE); +} + +bool autocvar_g_triggers_debug = true; + +void trigger_init(entity this) +{ + string m = this.model; + EXACTTRIGGER_INIT; + if(autocvar_g_triggers_debug) + { + if(m != "") + { + precache_model(m); + _setmodel(this, m); // no precision needed + } + setorigin(this, this.origin); + if(this.scale) + setsize(this, this.mins * this.scale, this.maxs * this.scale); + else + setsize(this, this.mins, this.maxs); + } + + if(autocvar_g_triggers_debug) + BITSET_ASSIGN(this.effects, EF_NODEPTHTEST); +} + +void trigger_link(entity this, bool(entity this, entity to, int sendflags) sendfunc) +{ + setSendEntity(this, sendfunc); + this.SendFlags = 0xFFFFFF; +} + +void trigger_common_write(entity this, bool withtarget) +{ + int f = 0; + if(this.warpzone_isboxy) + BITSET_ASSIGN(f, 1); + if(this.origin != '0 0 0') + BITSET_ASSIGN(f, 4); + if(this.movedir != '0 0 0') + BITSET_ASSIGN(f, 8); + if(this.angles != '0 0 0') + BITSET_ASSIGN(f, 16); + WriteByte(MSG_ENTITY, f); + + if(withtarget) + { + // probably some way to clean this up... + int targbits = 0; + if(this.target && this.target != "") targbits |= BIT(0); + if(this.target2 && this.target2 != "") targbits |= BIT(1); + if(this.target3 && this.target3 != "") targbits |= BIT(2); + if(this.target4 && this.target4 != "") targbits |= BIT(3); + if(this.targetname && this.targetname != "") targbits |= BIT(4); + if(this.killtarget && this.killtarget != "") targbits |= BIT(5); + + WriteByte(MSG_ENTITY, targbits); + + if(targbits & BIT(0)) + WriteString(MSG_ENTITY, this.target); + if(targbits & BIT(1)) + WriteString(MSG_ENTITY, this.target2); + if(targbits & BIT(2)) + WriteString(MSG_ENTITY, this.target3); + if(targbits & BIT(3)) + WriteString(MSG_ENTITY, this.target4); + if(targbits & BIT(4)) + WriteString(MSG_ENTITY, this.targetname); + if(targbits & BIT(5)) + WriteString(MSG_ENTITY, this.killtarget); + } + + if(f & 4) + WriteVector(MSG_ENTITY, this.origin); + + if(f & 8) + WriteVector(MSG_ENTITY, this.movedir); + + if(f & 16) + WriteVector(MSG_ENTITY, this.angles); + + WriteShort(MSG_ENTITY, this.modelindex); + WriteVector(MSG_ENTITY, this.mins); + WriteVector(MSG_ENTITY, this.maxs); + WriteByte(MSG_ENTITY, bound(1, this.scale * 16, 255)); +} + +#elif defined(CSQC) + +void trigger_common_read(entity this, bool withtarget) +{ + int f = ReadByte(); + this.warpzone_isboxy = (f & 1); + + if(withtarget) + { + strfree(this.target); + strfree(this.target2); + strfree(this.target3); + strfree(this.target4); + strfree(this.targetname); + strfree(this.killtarget); + + int targbits = ReadByte(); + + this.target = ((targbits & BIT(0)) ? strzone(ReadString()) : string_null); + this.target2 = ((targbits & BIT(1)) ? strzone(ReadString()) : string_null); + this.target3 = ((targbits & BIT(2)) ? strzone(ReadString()) : string_null); + this.target4 = ((targbits & BIT(3)) ? strzone(ReadString()) : string_null); + this.targetname = ((targbits & BIT(4)) ? strzone(ReadString()) : string_null); + this.killtarget = ((targbits & BIT(5)) ? strzone(ReadString()) : string_null); + } + + if(f & 4) + this.origin = ReadVector(); + else + this.origin = '0 0 0'; + setorigin(this, this.origin); + + if(f & 8) + this.movedir = ReadVector(); + else + this.movedir = '0 0 0'; + + if(f & 16) + this.angles = ReadVector(); + else + this.angles = '0 0 0'; + + this.modelindex = ReadShort(); + this.mins = ReadVector(); + this.maxs = ReadVector(); + this.scale = ReadByte() / 16; + setsize(this, this.mins, this.maxs); +} + +void trigger_remove_generic(entity this) +{ + strfree(this.target); + strfree(this.target2); + strfree(this.target3); + strfree(this.target4); + strfree(this.targetname); + strfree(this.killtarget); +} +#endif + + +/* +============================== +SUB_UseTargets + +the global "activator" should be set to the entity that initiated the firing. + +If this.delay is set, a DelayedUse entity will be created that will actually +do the SUB_UseTargets after that many seconds have passed. + +Centerprints any this.message to the activator. + +Removes all entities with a targetname that match this.killtarget, +and removes them, so some events can remove other triggers. + +Search for (string)targetname in all entities that +match (string)this.target and call their .use function + +============================== +*/ + +void SUB_UseTargets_Ex(entity this, entity actor, entity trigger, bool preventReuse) +{ +// +// check for a delay +// + if (this.delay) + { + // create a temp object to fire at a later time + entity t = new(DelayedUse); + t.nextthink = time + this.delay; + setthink(t, DelayThink); + t.enemy = actor; + t.message = this.message; + t.killtarget = this.killtarget; + t.target = this.target; + t.target2 = this.target2; + t.target3 = this.target3; + t.target4 = this.target4; + t.antiwall_flag = this.antiwall_flag; + return; + } + + string s; + +// +// print the message +// +#ifdef SVQC + if(this) + if(IS_PLAYER(actor) && this.message != "") + if(IS_REAL_CLIENT(actor)) + { + centerprint(actor, this.message); + if (this.noise == "") + play2(actor, SND(TALK)); + } + +// +// kill the killtagets +// + s = this.killtarget; + if (s != "") + { + for(entity t = NULL; (t = find(t, targetname, s)); ) + delete(t); + } +#endif + +// +// fire targets +// + + if(this.target_random) + RandomSelection_Init(); + + for(int i = 0; i < 4; ++i) + { + switch(i) + { + default: + case 0: s = this.target; break; + case 1: s = this.target2; break; + case 2: s = this.target3; break; + case 3: s = this.target4; break; + } + if (s != "") + { + // Flag to set func_clientwall state + // 1 == deactivate, 2 == activate, 0 == do nothing + int aw_flag = this.antiwall_flag; + for(entity t = NULL; (t = find(t, targetname, s)); ) + { + if(t.use && (t.sub_target_used != time || !preventReuse)) + { + if(this.target_random) + { + RandomSelection_AddEnt(t, 1, 0); + } + else + { + if (t.classname == "func_clientwall" || t.classname == "func_clientillusionary") + t.antiwall_flag = aw_flag; + + t.use(t, actor, this); + if(preventReuse) + t.sub_target_used = time; + } + } + } + } + } + + if(this.target_random && RandomSelection_chosen_ent) + { + RandomSelection_chosen_ent.use(RandomSelection_chosen_ent, actor, this); + if(preventReuse) + RandomSelection_chosen_ent.sub_target_used = time; + } +} + +void SUB_UseTargets(entity this, entity actor, entity trigger) { SUB_UseTargets_Ex(this, actor, trigger, false); } +void SUB_UseTargets_PreventReuse(entity this, entity actor, entity trigger) { SUB_UseTargets_Ex(this, actor, trigger, true); } diff --git a/qcsrc/common/mapobjects/triggers.qh b/qcsrc/common/mapobjects/triggers.qh new file mode 100644 index 000000000..82e7d54f0 --- /dev/null +++ b/qcsrc/common/mapobjects/triggers.qh @@ -0,0 +1,52 @@ +#pragma once +#include "defs.qh" + +.bool pushable; + +.float antiwall_flag; // Variable to define what to do with func_clientwall +// 0 == do nothing, 1 == deactivate, 2 == activate + +.float height; + +#define IFTARGETED if(this.targetname && this.targetname != "") + +.float lip; + +// used elsewhere (will fix) +#ifdef SVQC +void trigger_common_write(entity this, bool withtarget); + +string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin); + +void target_voicescript_next(entity pl); +void target_voicescript_clear(entity pl); + +void SUB_DontUseTargets(entity this, entity actor, entity trigger); +void SUB_UseTargets(entity this, entity actor, entity trigger); + +void SUB_UseTargets_PreventReuse(entity this, entity actor, entity trigger); + +void generic_setactive(entity this, int act); +// generic methods for netlinked entities +void generic_netlinked_reset(entity this); +void generic_netlinked_setactive(entity this, int act); +// WARNING: DON'T USE, ONLY TO KEEP COMPATIBILITY BECAUSE OF SWITCH FROM .state TO .alive!!!! +void generic_netlinked_legacy_use(entity this, entity actor, entity trigger); +#endif + +.float sub_target_used; + +.float volume, atten; + +.vector dest; + +void FixSize(entity e); + +#ifdef CSQC +void trigger_common_read(entity this, bool withtarget); +void trigger_remove_generic(entity this); + +.float active; +.string target; +.string targetname; +#endif diff --git a/qcsrc/common/monsters/monster/mage.qc b/qcsrc/common/monsters/monster/mage.qc index 82b7d273c..b8b647bef 100644 --- a/qcsrc/common/monsters/monster/mage.qc +++ b/qcsrc/common/monsters/monster/mage.qc @@ -101,7 +101,15 @@ bool M_Mage_Defend_Heal_Check(entity this, entity targ) switch(this.skin) { case 0: return (targ.health < autocvar_g_balance_health_regenstable); - case 1: return ((targ.ammo_cells && targ.ammo_cells < g_pickup_cells_max) || (targ.ammo_plasma && targ.ammo_plasma < g_pickup_plasma_max) || (targ.ammo_rockets && targ.ammo_rockets < g_pickup_rockets_max) || (targ.ammo_nails && targ.ammo_nails < g_pickup_nails_max) || (targ.ammo_shells && targ.ammo_shells < g_pickup_shells_max)); + case 1: + { + return ((GetResourceAmount(targ, RESOURCE_CELLS) && GetResourceAmount(targ, RESOURCE_CELLS) < g_pickup_cells_max) + || (GetResourceAmount(targ, RESOURCE_PLASMA) && GetResourceAmount(targ, RESOURCE_PLASMA) < g_pickup_plasma_max) + || (GetResourceAmount(targ, RESOURCE_ROCKETS) && GetResourceAmount(targ, RESOURCE_ROCKETS) < g_pickup_rockets_max) + || (GetResourceAmount(targ, RESOURCE_BULLETS) && GetResourceAmount(targ, RESOURCE_BULLETS) < g_pickup_nails_max) + || (GetResourceAmount(targ, RESOURCE_SHELLS) && GetResourceAmount(targ, RESOURCE_SHELLS) < g_pickup_shells_max) + ); + } case 2: return (targ.armorvalue < autocvar_g_balance_armor_regenstable); case 3: return (targ.health > 0); } @@ -230,13 +238,16 @@ void M_Mage_Defend_Heal(entity this) fx = EFFECT_HEALING; break; case 1: - if(it.ammo_cells) it.ammo_cells = bound(it.ammo_cells, it.ammo_cells + 1, g_pickup_cells_max); - if(it.ammo_plasma) it.ammo_plasma = bound(it.ammo_plasma, it.ammo_plasma + 1, g_pickup_plasma_max); - if(it.ammo_rockets) it.ammo_rockets = bound(it.ammo_rockets, it.ammo_rockets + 1, g_pickup_rockets_max); - if(it.ammo_shells) it.ammo_shells = bound(it.ammo_shells, it.ammo_shells + 2, g_pickup_shells_max); - if(it.ammo_nails) it.ammo_nails = bound(it.ammo_nails, it.ammo_nails + 5, g_pickup_nails_max); + { + float tmpfld; + tmpfld = GetResourceAmount(it, RESOURCE_CELLS); if(tmpfld) SetResourceAmount(it, RESOURCE_CELLS, bound(tmpfld, tmpfld + 1, g_pickup_cells_max)); + tmpfld = GetResourceAmount(it, RESOURCE_PLASMA); if(tmpfld) SetResourceAmount(it, RESOURCE_PLASMA, bound(tmpfld, tmpfld + 1, g_pickup_plasma_max)); + tmpfld = GetResourceAmount(it, RESOURCE_ROCKETS); if(tmpfld) SetResourceAmount(it, RESOURCE_ROCKETS, bound(tmpfld, tmpfld + 1, g_pickup_rockets_max)); + tmpfld = GetResourceAmount(it, RESOURCE_SHELLS); if(tmpfld) SetResourceAmount(it, RESOURCE_SHELLS, bound(tmpfld, tmpfld + 2, g_pickup_shells_max)); + tmpfld = GetResourceAmount(it, RESOURCE_BULLETS); if(tmpfld) SetResourceAmount(it, RESOURCE_BULLETS, bound(tmpfld, tmpfld + 5, g_pickup_nails_max)); fx = EFFECT_AMMO_REGEN; break; + } case 2: if(it.armorvalue < autocvar_g_balance_armor_regenstable) { diff --git a/qcsrc/common/monsters/sv_monsters.qc b/qcsrc/common/monsters/sv_monsters.qc index ccd10ee45..b4861b917 100644 --- a/qcsrc/common/monsters/sv_monsters.qc +++ b/qcsrc/common/monsters/sv_monsters.qc @@ -1,6 +1,5 @@ #include "sv_monsters.qh" -#include #include #include "../constants.qh" #include "../teams.qh" @@ -18,7 +17,7 @@ #include "../vehicles/all.qh" #include #include -#include "../triggers/triggers.qh" +#include "../mapobjects/triggers.qh" #include #include #include diff --git a/qcsrc/common/monsters/sv_spawner.qh b/qcsrc/common/monsters/sv_spawner.qh index 6f70f09be..1fd3ec672 100644 --- a/qcsrc/common/monsters/sv_spawner.qh +++ b/qcsrc/common/monsters/sv_spawner.qh @@ -1 +1,3 @@ #pragma once + +.string spawnmob; diff --git a/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc b/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc index 5afb96507..4be441dc1 100644 --- a/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc +++ b/qcsrc/common/mutators/mutator/buffs/sv_buffs.qc @@ -1,6 +1,6 @@ #include "sv_buffs.qh" -#include +#include #include void buffs_DelayedInit(entity this); diff --git a/qcsrc/common/mutators/mutator/instagib/items.qh b/qcsrc/common/mutators/mutator/instagib/items.qh index fe0070afc..95f4f3210 100644 --- a/qcsrc/common/mutators/mutator/instagib/items.qh +++ b/qcsrc/common/mutators/mutator/instagib/items.qh @@ -16,7 +16,7 @@ SOUND(VaporizerCells, Item_Sound("itempickup")); #ifdef SVQC int autocvar_g_instagib_ammo_drop; -void ammo_vaporizercells_init(entity item) +void ammo_vaporizercells_init(Pickup this, entity item) { if(!item.ammo_cells) item.ammo_cells = autocvar_g_instagib_ammo_drop; @@ -25,7 +25,7 @@ void ammo_vaporizercells_init(entity item) REGISTER_ITEM(VaporizerCells, Ammo) { this.m_canonical_spawnfunc = "item_vaporizer_cells"; #ifdef GAMEQC - this.spawnflags = ITEM_FLAG_INSTAGIB | ITEM_FLAG_MUTATORBLOCKED; + this.spawnflags = ITEM_FLAG_MUTATORBLOCKED; this.m_model = MDL_VaporizerCells_ITEM; this.m_sound = SND_VaporizerCells; #endif @@ -52,8 +52,7 @@ SOUND(ExtraLife, Item_Sound("megahealth")); REGISTER_ITEM(ExtraLife, Powerup) { this.m_canonical_spawnfunc = "item_extralife"; #ifdef GAMEQC - this.spawnflags = ITEM_FLAG_INSTAGIB; - this.m_model = MDL_ExtraLife_ITEM; + this.m_model = MDL_ExtraLife_ITEM; this.m_sound = SND_ExtraLife; #endif this.netname = "extralife"; @@ -76,13 +75,13 @@ SOUND(Invisibility, Item_Sound("powerup")); /// \brief Initializes the invisibility powerup. /// \param[in,out] item Item to initialize. /// \return No return. -void powerup_invisibility_init(entity item); +void powerup_invisibility_init(Pickup this, entity item); #endif REGISTER_ITEM(Invisibility, Powerup) { this.m_canonical_spawnfunc = "item_invisibility"; #ifdef GAMEQC - this.spawnflags = ITEM_FLAG_INSTAGIB | ITEM_FLAG_MUTATORBLOCKED; + this.spawnflags = ITEM_FLAG_MUTATORBLOCKED; this.m_model = MDL_Invisibility_ITEM; this.m_sound = SND_Invisibility; this.m_glow = true; @@ -111,13 +110,13 @@ SOUND(Speed, Item_Sound("powerup_shield")); /// \brief Initializes the speed powerup. /// \param[in,out] item Item to initialize. /// \return No return. -void powerup_speed_init(entity item); +void powerup_speed_init(Pickup this, entity item); #endif REGISTER_ITEM(Speed, Powerup) { this.m_canonical_spawnfunc = "item_speed"; #ifdef GAMEQC - this.spawnflags = ITEM_FLAG_INSTAGIB | ITEM_FLAG_MUTATORBLOCKED; + this.spawnflags = ITEM_FLAG_MUTATORBLOCKED; this.m_model = MDL_Speed_ITEM; this.m_sound = SND_Speed; this.m_glow = true; diff --git a/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc b/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc index f93b69235..3cda4485f 100644 --- a/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc +++ b/qcsrc/common/mutators/mutator/instagib/sv_instagib.qc @@ -1,5 +1,9 @@ #include "sv_instagib.qh" +#include +#include +#include "../random_items/sv_random_items.qh" + bool autocvar_g_instagib_damagedbycontents = true; bool autocvar_g_instagib_blaster_keepdamage = false; bool autocvar_g_instagib_blaster_keepforce = false; @@ -13,9 +17,15 @@ bool autocvar_g_instagib_ammo_convert_bullets; int autocvar_g_instagib_extralives; float autocvar_g_instagib_speed_highspeed; -#include - -#include +IntrusiveList g_instagib_items; +STATIC_INIT() +{ + g_instagib_items = IL_NEW(); + IL_PUSH(g_instagib_items, ITEM_VaporizerCells); + IL_PUSH(g_instagib_items, ITEM_ExtraLife); + IL_PUSH(g_instagib_items, ITEM_Invisibility); + IL_PUSH(g_instagib_items, ITEM_Speed); +} void instagib_invisibility(entity this) { @@ -34,6 +44,26 @@ void instagib_speed(entity this) StartItem(this, ITEM_Speed); } +/// \brief Returns a random classname of the instagib item. +/// \param[in] prefix Prefix of the cvars that hold probabilities. +/// \return Random classname of the instagib item. +string RandomItems_GetRandomInstagibItemClassName(string prefix) +{ + RandomSelection_Init(); + IL_EACH(g_instagib_items, Item_IsDefinitionAllowed(it), + { + string cvar_name = sprintf("g_%s_%s_probability", prefix, + it.m_canonical_spawnfunc); + if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS)) + { + LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name); + continue; + } + RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1); + }); + return RandomSelection_chosen_string; +} + .float instagib_nextthink; .float instagib_needammo; void instagib_stop_countdown(entity e) @@ -131,6 +161,13 @@ MUTATOR_HOOKFUNCTION(mutator_instagib, MatchEnd) FOREACH_CLIENT(IS_PLAYER(it), { instagib_stop_countdown(it); }); } +MUTATOR_HOOKFUNCTION(mutator_instagib, RandomItems_GetRandomItemClassName) +{ + M_ARGV(1, string) = RandomItems_GetRandomInstagibItemClassName( + M_ARGV(0, string)); + return true; +} + MUTATOR_HOOKFUNCTION(mutator_instagib, MonsterDropItem) { entity item = M_ARGV(1, entity); diff --git a/qcsrc/common/mutators/mutator/instagib/sv_items.qc b/qcsrc/common/mutators/mutator/instagib/sv_items.qc index 5c0d29dd3..c944f56c3 100644 --- a/qcsrc/common/mutators/mutator/instagib/sv_items.qc +++ b/qcsrc/common/mutators/mutator/instagib/sv_items.qc @@ -7,7 +7,7 @@ float autocvar_g_instagib_invisibility_time; /// \brief Time of speed powerup in seconds. float autocvar_g_instagib_speed_time; -void powerup_invisibility_init(entity item) +void powerup_invisibility_init(Pickup this, entity item) { if(!item.strength_finished) { @@ -16,7 +16,7 @@ void powerup_invisibility_init(entity item) } -void powerup_speed_init(entity item) +void powerup_speed_init(Pickup this, entity item) { if(!item.invincible_finished) { diff --git a/qcsrc/common/mutators/mutator/nades/nades.qc b/qcsrc/common/mutators/mutator/nades/nades.qc index dbd04a70a..7da7c0709 100644 --- a/qcsrc/common/mutators/mutator/nades/nades.qc +++ b/qcsrc/common/mutators/mutator/nades/nades.qc @@ -158,7 +158,6 @@ void DrawAmmoNades(vector myPos, vector mySize, bool draw_expanding, float expan #include #include #include -#include REGISTER_MUTATOR(nades, autocvar_g_nades); diff --git a/qcsrc/common/mutators/mutator/overkill/_mod.inc b/qcsrc/common/mutators/mutator/overkill/_mod.inc index 57fb6c246..017cc2082 100644 --- a/qcsrc/common/mutators/mutator/overkill/_mod.inc +++ b/qcsrc/common/mutators/mutator/overkill/_mod.inc @@ -10,3 +10,6 @@ #include #include #include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/mutators/mutator/overkill/okmachinegun.qh b/qcsrc/common/mutators/mutator/overkill/okmachinegun.qh index 9279c0c65..997b49de9 100644 --- a/qcsrc/common/mutators/mutator/overkill/okmachinegun.qh +++ b/qcsrc/common/mutators/mutator/overkill/okmachinegun.qh @@ -13,7 +13,7 @@ CLASS(OverkillMachineGun, Weapon) #endif /* crosshair */ ATTRIB(OverkillMachineGun, w_crosshair, string, "gfx/crosshairuzi"); /* crosshair */ ATTRIB(OverkillMachineGun, w_crosshair_size, float, 0.6); -/* wepimg */ ATTRIB(OverkillMachineGun, model2, string, "weaponuzi"); +/* wepimg */ ATTRIB(OverkillMachineGun, model2, string, "ok_weapon_smg"); /* refname */ ATTRIB(OverkillMachineGun, netname, string, "okmachinegun"); /* wepname */ ATTRIB(OverkillMachineGun, m_name, string, _("Overkill MachineGun")); diff --git a/qcsrc/common/mutators/mutator/overkill/oknex.qh b/qcsrc/common/mutators/mutator/overkill/oknex.qh index 617ce9dc5..f38588e39 100644 --- a/qcsrc/common/mutators/mutator/overkill/oknex.qh +++ b/qcsrc/common/mutators/mutator/overkill/oknex.qh @@ -14,7 +14,7 @@ CLASS(OverkillNex, Weapon) /* crosshair */ ATTRIB(OverkillNex, w_crosshair, string, "gfx/crosshairnex"); /* crosshair */ ATTRIB(OverkillNex, w_crosshair_size, float, 0.65); /* reticle */ ATTRIB(OverkillNex, w_reticle, string, "gfx/reticle_nex"); -/* wepimg */ ATTRIB(OverkillNex, model2, string, "weaponnex"); +/* wepimg */ ATTRIB(OverkillNex, model2, string, "ok_weapon_rail"); /* refname */ ATTRIB(OverkillNex, netname, string, "oknex"); /* wepname */ ATTRIB(OverkillNex, m_name, string, _("Overkill Nex")); diff --git a/qcsrc/common/mutators/mutator/overkill/okshotgun.qh b/qcsrc/common/mutators/mutator/overkill/okshotgun.qh index dc6b44ee2..a383c9d7a 100644 --- a/qcsrc/common/mutators/mutator/overkill/okshotgun.qh +++ b/qcsrc/common/mutators/mutator/overkill/okshotgun.qh @@ -13,7 +13,7 @@ CLASS(OverkillShotgun, Weapon) #endif /* crosshair */ ATTRIB(OverkillShotgun, w_crosshair, string, "gfx/crosshairshotgun"); /* crosshair */ ATTRIB(OverkillShotgun, w_crosshair_size, float, 0.65); -/* wepimg */ ATTRIB(OverkillShotgun, model2, string, "weaponshotgun"); +/* wepimg */ ATTRIB(OverkillShotgun, model2, string, "ok_weapon_shotgun"); /* refname */ ATTRIB(OverkillShotgun, netname, string, "okshotgun"); /* wepname */ ATTRIB(OverkillShotgun, m_name, string, _("Overkill Shotgun")); diff --git a/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc b/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc index f4432a9cb..9fa66e8b2 100644 --- a/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc +++ b/qcsrc/common/mutators/mutator/overkill/sv_overkill.qc @@ -1,11 +1,73 @@ #include "sv_overkill.qh" +#include "okshotgun.qh" +#include "okhmg.qh" +#include "okrpc.qh" + bool autocvar_g_overkill_powerups_replace; bool autocvar_g_overkill_itemwaypoints = true; .Weapon ok_lastwep[MAX_WEAPONSLOTS]; +IntrusiveList g_overkill_items; +STATIC_INIT() +{ + g_overkill_items = IL_NEW(); + IL_PUSH(g_overkill_items, ITEM_HealthMega); + IL_PUSH(g_overkill_items, ITEM_ArmorSmall); + IL_PUSH(g_overkill_items, ITEM_ArmorMedium); + IL_PUSH(g_overkill_items, ITEM_ArmorBig); + IL_PUSH(g_overkill_items, ITEM_ArmorMega); +} + +/// \brief Returns a random classname of the overkill item. +/// \param[in] prefix Prefix of the cvars that hold probabilities. +/// \return Random classname of the overkill item. +string RandomItems_GetRandomOverkillItemClassName(string prefix) +{ + RandomSelection_Init(); + IL_EACH(g_overkill_items, !(it.spawnflags & ITEM_FLAG_MUTATORBLOCKED) && + Item_IsDefinitionAllowed(it), + { + string cvar_name = sprintf("g_%s_%s_probability", prefix, + it.m_canonical_spawnfunc); + if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS)) + { + LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name); + continue; + } + RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1); + }); + string cvar_name = sprintf("g_%s_weapon_okhmg_probability", prefix); + if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS)) + { + LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name); + } + else + { + RandomSelection_AddString("weapon_okhmg", cvar(cvar_name), 1); + } + cvar_name = sprintf("g_%s_weapon_okrpc_probability", prefix); + if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS)) + { + LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name); + } + else + { + RandomSelection_AddString("weapon_okrpc", cvar(cvar_name), 1); + } + return RandomSelection_chosen_string; +} + + +MUTATOR_HOOKFUNCTION(ok, RandomItems_GetRandomItemClassName) +{ + M_ARGV(1, string) = RandomItems_GetRandomOverkillItemClassName( + M_ARGV(0, string)); + return true; +} + MUTATOR_HOOKFUNCTION(ok, Damage_Calculate, CBC_ORDER_LAST) { entity frag_attacker = M_ARGV(1, entity); diff --git a/qcsrc/common/mutators/mutator/overkill/sv_overkill.qh b/qcsrc/common/mutators/mutator/overkill/sv_overkill.qh index 4949edb1e..79e5dd3c5 100644 --- a/qcsrc/common/mutators/mutator/overkill/sv_overkill.qh +++ b/qcsrc/common/mutators/mutator/overkill/sv_overkill.qh @@ -1,10 +1,5 @@ #pragma once -#include "okshotgun.qh" -#include "okmachinegun.qh" -#include "okhmg.qh" -#include "okrpc.qh" - string autocvar_g_overkill; bool autocvar_g_overkill_filter_healthmega; bool autocvar_g_overkill_filter_armormedium; @@ -35,13 +30,6 @@ REGISTER_MUTATOR(ok, expr_evaluate(autocvar_g_overkill) && !MUTATOR_IS_ENABLED(m { ITEM_ArmorMega.spawnflags |= ITEM_FLAG_MUTATORBLOCKED; } - - WEP_OVERKILL_RPC.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; - WEP_OVERKILL_HMG.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; - - WEP_OVERKILL_SHOTGUN.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; - WEP_OVERKILL_MACHINEGUN.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; - WEP_OVERKILL_NEX.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; } MUTATOR_ONREMOVE @@ -50,12 +38,5 @@ REGISTER_MUTATOR(ok, expr_evaluate(autocvar_g_overkill) && !MUTATOR_IS_ENABLED(m ITEM_ArmorMedium.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED; ITEM_ArmorBig.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED; ITEM_ArmorMega.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED; - - WEP_OVERKILL_RPC.spawnflags |= WEP_FLAG_MUTATORBLOCKED; - WEP_OVERKILL_HMG.spawnflags |= WEP_FLAG_MUTATORBLOCKED; - - WEP_OVERKILL_SHOTGUN.spawnflags |= WEP_FLAG_MUTATORBLOCKED; - WEP_OVERKILL_MACHINEGUN.spawnflags |= WEP_FLAG_MUTATORBLOCKED; - WEP_OVERKILL_NEX.spawnflags |= WEP_FLAG_MUTATORBLOCKED; } } diff --git a/qcsrc/common/mutators/mutator/overkill/sv_weapons.qc b/qcsrc/common/mutators/mutator/overkill/sv_weapons.qc new file mode 100644 index 000000000..4a131e3f0 --- /dev/null +++ b/qcsrc/common/mutators/mutator/overkill/sv_weapons.qc @@ -0,0 +1,22 @@ +string autocvar_g_overkill_weapons; + +REGISTER_MUTATOR(ok_weapons, expr_evaluate(autocvar_g_overkill_weapons) || MUTATOR_IS_ENABLED(ok)) +{ + MUTATOR_ONADD + { + WEP_OVERKILL_SHOTGUN.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; + WEP_OVERKILL_MACHINEGUN.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; + WEP_OVERKILL_NEX.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; + WEP_OVERKILL_HMG.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; + WEP_OVERKILL_RPC.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; + } + + MUTATOR_ONREMOVE + { + WEP_OVERKILL_SHOTGUN.spawnflags |= WEP_FLAG_MUTATORBLOCKED; + WEP_OVERKILL_MACHINEGUN.spawnflags |= WEP_FLAG_MUTATORBLOCKED; + WEP_OVERKILL_NEX.spawnflags |= WEP_FLAG_MUTATORBLOCKED; + WEP_OVERKILL_HMG.spawnflags |= WEP_FLAG_MUTATORBLOCKED; + WEP_OVERKILL_RPC.spawnflags |= WEP_FLAG_MUTATORBLOCKED; + } +} diff --git a/qcsrc/common/mutators/mutator/random_items/sv_random_items.qc b/qcsrc/common/mutators/mutator/random_items/sv_random_items.qc index 251e57b87..183808021 100644 --- a/qcsrc/common/mutators/mutator/random_items/sv_random_items.qc +++ b/qcsrc/common/mutators/mutator/random_items/sv_random_items.qc @@ -53,13 +53,9 @@ string RandomItems_GetRandomItemClassNameWithProperty(string prefix, string RandomItems_GetRandomItemClassName(string prefix) { - if (MUTATOR_IS_ENABLED(mutator_instagib)) + if (MUTATOR_CALLHOOK(RandomItems_GetRandomItemClassName, prefix)) { - return RandomItems_GetRandomInstagibItemClassName(prefix); - } - if (MUTATOR_IS_ENABLED(ok)) - { - return RandomItems_GetRandomOverkillItemClassName(prefix); + return M_ARGV(1, string); } return RandomItems_GetRandomVanillaItemClassName(prefix, RANDOM_ITEM_TYPE_ALL); @@ -196,61 +192,6 @@ string RandomItems_GetRandomVanillaItemClassName(string prefix, int types) return ""; } -string RandomItems_GetRandomInstagibItemClassName(string prefix) -{ - RandomSelection_Init(); - FOREACH(Items, it.spawnflags & ITEM_FLAG_INSTAGIB && - Item_IsDefinitionAllowed(it), - { - string cvar_name = sprintf("g_%s_%s_probability", prefix, - it.m_canonical_spawnfunc); - if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS)) - { - LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name); - continue; - } - RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1); - }); - return RandomSelection_chosen_string; -} - -string RandomItems_GetRandomOverkillItemClassName(string prefix) -{ - RandomSelection_Init(); - FOREACH(Items, (it.spawnflags & ITEM_FLAG_OVERKILL) && - !(it.spawnflags & ITEM_FLAG_MUTATORBLOCKED) && - Item_IsDefinitionAllowed(it), - { - string cvar_name = sprintf("g_%s_overkill_%s_probability", prefix, - it.m_canonical_spawnfunc); - if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS)) - { - LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name); - continue; - } - RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1); - }); - string cvar_name = sprintf("g_%s_overkill_weapon_okhmg_probability", prefix); - if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS)) - { - LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name); - } - else - { - RandomSelection_AddString("weapon_okhmg", cvar(cvar_name), 1); - } - cvar_name = sprintf("g_%s_overkill_weapon_okrpc_probability", prefix); - if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS)) - { - LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name); - } - else - { - RandomSelection_AddString("weapon_okrpc", cvar(cvar_name), 1); - } - return RandomSelection_chosen_string; -} - //========================= Free functions ==================================== /// \brief Returns list of classnames to replace a map item with. diff --git a/qcsrc/common/mutators/mutator/random_items/sv_random_items.qh b/qcsrc/common/mutators/mutator/random_items/sv_random_items.qh index d49e3effa..c94375cea 100644 --- a/qcsrc/common/mutators/mutator/random_items/sv_random_items.qh +++ b/qcsrc/common/mutators/mutator/random_items/sv_random_items.qh @@ -33,15 +33,13 @@ string RandomItems_GetRandomItemClassName(string prefix); /// jetpack and new toys. string RandomItems_GetRandomVanillaItemClassName(string prefix, int types); -/// \brief Returns a random classname of the instagib item. -/// \param[in] prefix Prefix of the cvars that hold probabilities. -/// \return Random classname of the instagib item. -string RandomItems_GetRandomInstagibItemClassName(string prefix); - -/// \brief Returns a random classname of the overkill item. -/// \param[in] prefix Prefix of the cvars that hold probabilities. -/// \return Random classname of the overkill item. -string RandomItems_GetRandomOverkillItemClassName(string prefix); +/// \brief Called when random item classname is requested. +#define EV_RandomItems_GetRandomItemClassName(i, o) \ + /** prefix */ i(string, MUTATOR_ARGV_0_string) \ + /** classname */ o(string, MUTATOR_ARGV_1_string) \ + /**/ +MUTATOR_HOOKABLE(RandomItems_GetRandomItemClassName, + EV_RandomItems_GetRandomItemClassName); REGISTER_MUTATOR(random_items, (autocvar_g_random_items || autocvar_g_random_loot)); diff --git a/qcsrc/common/mutators/mutator/weaponarena_random/sv_weaponarena_random.qc b/qcsrc/common/mutators/mutator/weaponarena_random/sv_weaponarena_random.qc index f4f7e3447..32b6f3930 100644 --- a/qcsrc/common/mutators/mutator/weaponarena_random/sv_weaponarena_random.qc +++ b/qcsrc/common/mutators/mutator/weaponarena_random/sv_weaponarena_random.qc @@ -3,6 +3,16 @@ // WEAPONTODO: rename the cvars REGISTER_MUTATOR(weaponarena_random, true); +MUTATOR_HOOKFUNCTION(weaponarena_random, SetStartItems) +{ + if(g_weaponarena) + g_weaponarena_random = cvar("g_weaponarena_random"); + else + g_weaponarena_random = 0; + + g_weaponarena_random_with_blaster = cvar("g_weaponarena_random_with_blaster"); +} + MUTATOR_HOOKFUNCTION(weaponarena_random, PlayerSpawn) { if (!g_weaponarena_random) return; @@ -12,3 +22,54 @@ MUTATOR_HOOKFUNCTION(weaponarena_random, PlayerSpawn) STAT(WEAPONS, player) = W_RandomWeapons(player, STAT(WEAPONS, player), g_weaponarena_random); if (g_weaponarena_random_with_blaster) STAT(WEAPONS, player) |= WEPSET(BLASTER); } + +MUTATOR_HOOKFUNCTION(weaponarena_random, GiveFragsForKill) +{ + if(!g_weaponarena_random) return; + entity attacker = M_ARGV(0, entity); + entity targ = M_ARGV(1, entity); + float deathtype = M_ARGV(3, float); + entity wep_ent = M_ARGV(4, entity); + .entity weaponentity = wep_ent.weaponentity_fld; + + if(targ == attacker) return; // not for suicides + + // after a frag, exchange the current weapon (or the culprit, if detectable) by a new random weapon + Weapon culprit = DEATH_WEAPONOF(deathtype); + if(!culprit) culprit = wep_ent.m_weapon; + else if(!(STAT(WEAPONS, attacker) & (culprit.m_wepset))) culprit = wep_ent.m_weapon; + + if(g_weaponarena_random_with_blaster && culprit == WEP_BLASTER) + { + // no exchange + } + else + { + if(!GiveFrags_randomweapons) + { + GiveFrags_randomweapons = new(GiveFrags_randomweapons); + } + + if(warmup_stage) + STAT(WEAPONS, GiveFrags_randomweapons) = WARMUP_START_WEAPONS; + else + STAT(WEAPONS, GiveFrags_randomweapons) = start_weapons; + + // all others (including the culprit): remove + STAT(WEAPONS, GiveFrags_randomweapons) &= ~STAT(WEAPONS, attacker); + STAT(WEAPONS, GiveFrags_randomweapons) &= ~(culprit.m_wepset); + + // among the remaining ones, choose one by random + STAT(WEAPONS, GiveFrags_randomweapons) = W_RandomWeapons(GiveFrags_randomweapons, STAT(WEAPONS, GiveFrags_randomweapons), 1); + + if(STAT(WEAPONS, GiveFrags_randomweapons)) + { + STAT(WEAPONS, attacker) |= STAT(WEAPONS, GiveFrags_randomweapons); + STAT(WEAPONS, attacker) &= ~(culprit.m_wepset); + } + } + + // after a frag, choose another random weapon set + if (!(STAT(WEAPONS, attacker) & WepSet_FromWeapon(wep_ent.m_weapon))) + W_SwitchWeapon_Force(attacker, w_getbestweapon(attacker, weaponentity), weaponentity); +} diff --git a/qcsrc/common/mutators/mutator/weaponarena_random/sv_weaponarena_random.qh b/qcsrc/common/mutators/mutator/weaponarena_random/sv_weaponarena_random.qh index 6f70f09be..4af3cf036 100644 --- a/qcsrc/common/mutators/mutator/weaponarena_random/sv_weaponarena_random.qh +++ b/qcsrc/common/mutators/mutator/weaponarena_random/sv_weaponarena_random.qh @@ -1 +1,5 @@ #pragma once + +float g_weaponarena_random_with_blaster; + +entity GiveFrags_randomweapons; diff --git a/qcsrc/common/net_linked.qh b/qcsrc/common/net_linked.qh index 657e76171..6651c6cb9 100644 --- a/qcsrc/common/net_linked.qh +++ b/qcsrc/common/net_linked.qh @@ -22,6 +22,7 @@ const int RACE_NET_SERVER_RANKINGS = 11; const int RACE_NET_SERVER_STATUS = 12; const int RACE_NET_CHECKPOINT_HIT_SELF_QUALIFYING = 13; // byte checkpoint, short time, short recordtime const int RACE_NET_CHECKPOINT_NEXT_SELF_QUALIFYING = 14; // byte nextcheckpoint, short recordtime +const int RACE_NET_RANKINGS_CNT = 15; REGISTER_NET_LINKED(_ENT_CLIENT_INIT) #ifdef CSQC diff --git a/qcsrc/common/notifications/all.qh b/qcsrc/common/notifications/all.qh index 12bd3b683..7982ee01f 100644 --- a/qcsrc/common/notifications/all.qh +++ b/qcsrc/common/notifications/all.qh @@ -5,6 +5,7 @@ #include #include #include +#include #ifdef CSQC #include diff --git a/qcsrc/common/physics/player.qc b/qcsrc/common/physics/player.qc index 53765f419..2b9808a55 100644 --- a/qcsrc/common/physics/player.qc +++ b/qcsrc/common/physics/player.qc @@ -1,11 +1,11 @@ #include "player.qh" -#include "../triggers/include.qh" +#include "../mapobjects/_mod.qh" #include "../viewloc.qh" #ifdef SVQC #include -#include "../triggers/trigger/viewloc.qh" +#include "../mapobjects/trigger/viewloc.qh" // client side physics bool Physics_Valid(string thecvar) diff --git a/qcsrc/common/t_items.qc b/qcsrc/common/t_items.qc index 8be48b530..f4ed4f1bf 100644 --- a/qcsrc/common/t_items.qc +++ b/qcsrc/common/t_items.qc @@ -15,7 +15,7 @@ #include "constants.qh" #include #include - #include "triggers/subs.qh" + #include "mapobjects/subs.qh" #include "util.qh" #include @@ -1103,23 +1103,23 @@ float ammo_pickupevalfunc(entity player, entity item) float noammorating = 0.5; - if ((need_shells) && (item.ammo_shells) && (player.ammo_shells < g_pickup_shells_max)) - c = item.ammo_shells / max(noammorating, player.ammo_shells); + if ((need_shells) && (item.ammo_shells) && (GetResourceAmount(player, RESOURCE_SHELLS) < g_pickup_shells_max)) + c = item.ammo_shells / max(noammorating, GetResourceAmount(player, RESOURCE_SHELLS)); - if ((need_nails) && (item.ammo_nails) && (player.ammo_nails < g_pickup_nails_max)) - c = item.ammo_nails / max(noammorating, player.ammo_nails); + if ((need_nails) && (item.ammo_nails) && (GetResourceAmount(player, RESOURCE_BULLETS) < g_pickup_nails_max)) + c = item.ammo_nails / max(noammorating, GetResourceAmount(player, RESOURCE_BULLETS)); - if ((need_rockets) && (item.ammo_rockets) && (player.ammo_rockets < g_pickup_rockets_max)) - c = item.ammo_rockets / max(noammorating, player.ammo_rockets); + if ((need_rockets) && (item.ammo_rockets) && (GetResourceAmount(player, RESOURCE_ROCKETS) < g_pickup_rockets_max)) + c = item.ammo_rockets / max(noammorating, GetResourceAmount(player, RESOURCE_ROCKETS)); - if ((need_cells) && (item.ammo_cells) && (player.ammo_cells < g_pickup_cells_max)) - c = item.ammo_cells / max(noammorating, player.ammo_cells); + if ((need_cells) && (item.ammo_cells) && (GetResourceAmount(player, RESOURCE_CELLS) < g_pickup_cells_max)) + c = item.ammo_cells / max(noammorating, GetResourceAmount(player, RESOURCE_CELLS)); - if ((need_plasma) && (item.ammo_plasma) && (player.ammo_plasma < g_pickup_plasma_max)) - c = item.ammo_plasma / max(noammorating, player.ammo_plasma); + if ((need_plasma) && (item.ammo_plasma) && (GetResourceAmount(player, RESOURCE_PLASMA) < g_pickup_plasma_max)) + c = item.ammo_plasma / max(noammorating, GetResourceAmount(player, RESOURCE_PLASMA)); - if ((need_fuel) && (item.ammo_fuel) && (player.ammo_fuel < g_pickup_fuel_max)) - c = item.ammo_fuel / max(noammorating, player.ammo_fuel); + if ((need_fuel) && (item.ammo_fuel) && (GetResourceAmount(player, RESOURCE_FUEL) < g_pickup_fuel_max)) + c = item.ammo_fuel / max(noammorating, GetResourceAmount(player, RESOURCE_FUEL)); rating *= min(c, 2); if(wpn) @@ -1178,7 +1178,7 @@ void _StartItem(entity this, entity def, float defaultrespawntime, float default this.item_pickupsound_ent = pickupsound; if(def.m_iteminit) - def.m_iteminit(this); + def.m_iteminit(def, this); if(!this.respawntime) // both need to be set { diff --git a/qcsrc/common/triggers/_mod.inc b/qcsrc/common/triggers/_mod.inc deleted file mode 100644 index 9b327de55..000000000 --- a/qcsrc/common/triggers/_mod.inc +++ /dev/null @@ -1,11 +0,0 @@ -// generated file; do not modify -#include -#include -#include -#include -#include - -#include -#include -#include -#include diff --git a/qcsrc/common/triggers/_mod.qh b/qcsrc/common/triggers/_mod.qh deleted file mode 100644 index d3bb2dc3a..000000000 --- a/qcsrc/common/triggers/_mod.qh +++ /dev/null @@ -1,11 +0,0 @@ -// generated file; do not modify -#include -#include -#include -#include -#include - -#include -#include -#include -#include diff --git a/qcsrc/common/triggers/defs.qh b/qcsrc/common/triggers/defs.qh deleted file mode 100644 index 45afb51f9..000000000 --- a/qcsrc/common/triggers/defs.qh +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -//----------- -// SPAWNFLAGS -//----------- -const int START_ENABLED = BIT(0); -const int START_DISABLED = BIT(0); -const int ALL_ENTITIES = BIT(1); -const int ON_MAPLOAD = BIT(1); -const int INVERT_TEAMS = BIT(2); -const int CRUSH = BIT(2); -const int NOSPLASH = BIT(8); // generic anti-splashdamage spawnflag -const int ONLY_PLAYERS = BIT(14); - -// triggers -const int SPAWNFLAG_NOMESSAGE = BIT(0); -const int SPAWNFLAG_NOTOUCH = BIT(0); - -//---------- -// SENDFLAGS -//---------- -const int SF_TRIGGER_INIT = BIT(0); -const int SF_TRIGGER_UPDATE = BIT(1); -const int SF_TRIGGER_RESET = BIT(2); - -//---------------- -// STATES & ACTIVE -//---------------- -#ifdef CSQC -// this stuff is defined in the server side engine VM, so we must define it separately here -const int STATE_TOP = 0; -const int STATE_BOTTOM = 1; -const int STATE_UP = 2; -const int STATE_DOWN = 3; - -const int ACTIVE_NOT = 0; -const int ACTIVE_ACTIVE = 1; -const int ACTIVE_IDLE = 2; -const int ACTIVE_BUSY = 2; -const int ACTIVE_TOGGLE = 3; -#endif diff --git a/qcsrc/common/triggers/func/_mod.inc b/qcsrc/common/triggers/func/_mod.inc deleted file mode 100644 index 675e3689b..000000000 --- a/qcsrc/common/triggers/func/_mod.inc +++ /dev/null @@ -1,19 +0,0 @@ -// generated file; do not modify -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include diff --git a/qcsrc/common/triggers/func/_mod.qh b/qcsrc/common/triggers/func/_mod.qh deleted file mode 100644 index fb179a42c..000000000 --- a/qcsrc/common/triggers/func/_mod.qh +++ /dev/null @@ -1,19 +0,0 @@ -// generated file; do not modify -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include diff --git a/qcsrc/common/triggers/func/bobbing.qc b/qcsrc/common/triggers/func/bobbing.qc deleted file mode 100644 index b647e15a8..000000000 --- a/qcsrc/common/triggers/func/bobbing.qc +++ /dev/null @@ -1,85 +0,0 @@ -#include "bobbing.qh" -#ifdef SVQC -.float height; -void func_bobbing_controller_think(entity this) -{ - vector v; - this.nextthink = time + 0.1; - - if(this.owner.active != ACTIVE_ACTIVE) - { - this.owner.velocity = '0 0 0'; - return; - } - - // calculate sinewave using makevectors - makevectors((this.nextthink * this.owner.cnt + this.owner.phase * 360) * '0 1 0'); - v = this.owner.destvec + this.owner.movedir * v_forward_y; - if(this.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed - // * 10 so it will arrive in 0.1 sec - this.owner.velocity = (v - this.owner.origin) * 10; -} - -/*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS -Brush model that moves back and forth on one axis (default Z). -speed : how long one cycle takes in seconds (default 4) -height : how far the cycle moves (default 32) -phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0) -noise : path/name of looping .wav file to play. -dmg : Do this mutch dmg every .dmgtime intervall when blocked -dmgtime : See above. -*/ -spawnfunc(func_bobbing) -{ - entity controller; - if (this.noise != "") - { - precache_sound(this.noise); - soundto(MSG_INIT, this, CH_TRIGGER_SINGLE, this.noise, VOL_BASE, ATTEN_IDLE); - } - if (!this.speed) - this.speed = 4; - if (!this.height) - this.height = 32; - // center of bobbing motion - this.destvec = this.origin; - // time scale to get degrees - this.cnt = 360 / this.speed; - - this.active = ACTIVE_ACTIVE; - - // damage when blocked - setblocked(this, generic_plat_blocked); - if(this.dmg && (this.message == "")) - this.message = " was squished"; - if(this.dmg && (this.message2 == "")) - this.message2 = "was squished by"; - if(this.dmg && (!this.dmgtime)) - this.dmgtime = 0.25; - this.dmgtime2 = time; - - // how far to bob - if (this.spawnflags & BOBBING_XAXIS) - this.movedir = '1 0 0' * this.height; - else if (this.spawnflags & BOBBING_YAXIS) - this.movedir = '0 1 0' * this.height; - else // Z - this.movedir = '0 0 1' * this.height; - - if (!InitMovingBrushTrigger(this)) - return; - - // wait for targets to spawn - controller = new(func_bobbing_controller); - controller.owner = this; - controller.nextthink = time + 1; - setthink(controller, func_bobbing_controller_think); - this.nextthink = this.ltime + 999999999; - setthink(this, SUB_NullThink); - - // Savage: Reduce bandwith, critical on e.g. nexdm02 - this.effects |= EF_LOWPRECISION; - - // TODO make a reset function for this one -} -#endif diff --git a/qcsrc/common/triggers/func/bobbing.qh b/qcsrc/common/triggers/func/bobbing.qh deleted file mode 100644 index 58f7bb714..000000000 --- a/qcsrc/common/triggers/func/bobbing.qh +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - - -const int BOBBING_XAXIS = BIT(0); -const int BOBBING_YAXIS = BIT(1); diff --git a/qcsrc/common/triggers/func/breakable.qc b/qcsrc/common/triggers/func/breakable.qc deleted file mode 100644 index d09ccd5e0..000000000 --- a/qcsrc/common/triggers/func/breakable.qc +++ /dev/null @@ -1,374 +0,0 @@ -#include "breakable.qh" -#ifdef SVQC - -#include -#include -#include -#include -#include -#include - -.entity sprite; - -.float dmg; -.float dmg_edge; -.float dmg_radius; -.float dmg_force; -.float debrismovetype; -.float debrissolid; -.vector debrisvelocity; -.vector debrisvelocityjitter; -.vector debrisavelocityjitter; -.float debristime; -.float debristimejitter; -.float debrisfadetime; -.float debrisdamageforcescale; -.float debrisskin; - -.string mdl_dead; // or "" to hide when broken -.string debris; // space separated list of debris models -// other fields: -// mdl = particle effect name -// count = particle effect multiplier -// targetname = target to trigger to unbreak the model -// target = targets to trigger when broken -// health = amount of damage it can take -// spawnflags: -// START_DISABLED: needs to be triggered to activate -// BREAKABLE_INDICATE_DAMAGE: indicate damage -// BREAKABLE_NODAMAGE: don't take direct damage (needs to be triggered to 'explode', then triggered again to restore) -// NOSPLASH: don't take splash damage -// notes: -// for mdl_dead to work, origin must be set (using a common/origin brush). -// Otherwise mdl_dead will be displayed at the map origin, and nobody would -// want that! - -void func_breakable_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force); - -// -// func_breakable -// - basically func_assault_destructible for general gameplay use -// -void LaunchDebris (entity this, string debrisname, vector force) -{ - entity dbr = spawn(); - vector org = this.absmin - + '1 0 0' * random() * (this.absmax.x - this.absmin.x) - + '0 1 0' * random() * (this.absmax.y - this.absmin.y) - + '0 0 1' * random() * (this.absmax.z - this.absmin.z); - setorigin(dbr, org); - _setmodel (dbr, debrisname ); - dbr.skin = this.debrisskin; - dbr.colormap = this.colormap; // inherit team colors - dbr.owner = this; // do not be affected by our own explosion - set_movetype(dbr, this.debrismovetype); - dbr.solid = this.debrissolid; - if(dbr.solid != SOLID_BSP) // SOLID_BSP has exact collision, MAYBE this works? TODO check this out - setsize(dbr, '0 0 0', '0 0 0'); // needed for performance, until engine can deal better with it - dbr.velocity_x = this.debrisvelocity.x + this.debrisvelocityjitter.x * crandom(); - dbr.velocity_y = this.debrisvelocity.y + this.debrisvelocityjitter.y * crandom(); - dbr.velocity_z = this.debrisvelocity.z + this.debrisvelocityjitter.z * crandom(); - dbr.velocity = dbr.velocity + force * this.debrisdamageforcescale; - dbr.angles = this.angles; - dbr.avelocity_x = random()*this.debrisavelocityjitter.x; - dbr.avelocity_y = random()*this.debrisavelocityjitter.y; - dbr.avelocity_z = random()*this.debrisavelocityjitter.z; - dbr.damageforcescale = this.debrisdamageforcescale; - if(dbr.damageforcescale) - dbr.takedamage = DAMAGE_YES; - SUB_SetFade(dbr, time + this.debristime + crandom() * this.debristimejitter, this.debrisfadetime); -} - -void func_breakable_colormod(entity this) -{ - float h; - if (!(this.spawnflags & BREAKABLE_INDICATE_DAMAGE)) - return; - h = this.health / this.max_health; - if(h < 0.25) - this.colormod = '1 0 0'; - else if(h <= 0.75) - this.colormod = '1 0 0' + '0 1 0' * (2 * h - 0.5); - else - this.colormod = '1 1 1'; -} - -void func_breakable_look_destroyed(entity this) -{ - float floorZ; - - if(this.solid == SOLID_BSP) // in case a misc_follow moved me, save the current origin first - this.dropped_origin = this.origin; - - if(this.mdl_dead == "") - this.effects |= EF_NODRAW; - else { - if (this.origin == '0 0 0') { // probably no origin brush, so don't spawn in the middle of the map.. - floorZ = this.absmin.z; - setorigin(this, ((this.absmax + this.absmin) * 0.5)); - this.origin_z = floorZ; - } - _setmodel(this, this.mdl_dead); - ApplyMinMaxScaleAngles(this); - this.effects &= ~EF_NODRAW; - } - - this.solid = SOLID_NOT; -} - -void func_breakable_look_restore(entity this) -{ - _setmodel(this, this.mdl); - ApplyMinMaxScaleAngles(this); - this.effects &= ~EF_NODRAW; - - if(this.mdl_dead != "") // only do this if we use mdl_dead, to behave better with misc_follow - setorigin(this, this.dropped_origin); - - this.solid = SOLID_BSP; -} - -void func_breakable_behave_destroyed(entity this) -{ - this.health = this.max_health; - this.takedamage = DAMAGE_NO; - if(this.bot_attack) - IL_REMOVE(g_bot_targets, this); - this.bot_attack = false; - this.event_damage = func_null; - this.state = STATE_BROKEN; - if(this.spawnflags & BREAKABLE_NODAMAGE) - this.use = func_null; - func_breakable_colormod(this); - if (this.noise1) - stopsound (this, CH_TRIGGER_SINGLE); -} - -void func_breakable_think(entity this) -{ - this.nextthink = time; - CSQCMODEL_AUTOUPDATE(this); -} - -void func_breakable_destroy(entity this, entity actor, entity trigger); -void func_breakable_behave_restore(entity this) -{ - this.health = this.max_health; - if(this.sprite) - { - WaypointSprite_UpdateMaxHealth(this.sprite, this.max_health); - WaypointSprite_UpdateHealth(this.sprite, this.health); - } - if(!(this.spawnflags & BREAKABLE_NODAMAGE)) - { - this.takedamage = DAMAGE_AIM; - if(!this.bot_attack) - IL_PUSH(g_bot_targets, this); - this.bot_attack = true; - this.event_damage = func_breakable_damage; - } - if(this.spawnflags & BREAKABLE_NODAMAGE) - this.use = func_breakable_destroy; // don't need to set it usually, as .use isn't reset - this.state = STATE_ALIVE; - //this.nextthink = 0; // cancel auto respawn - setthink(this, func_breakable_think); - this.nextthink = time + 0.1; - func_breakable_colormod(this); - if (this.noise1) - _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM); -} - -void func_breakable_init_for_player(entity this, entity player) -{ - if (this.noise1 && this.state == STATE_ALIVE && IS_REAL_CLIENT(player)) - { - msg_entity = player; - soundto (MSG_ONE, this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM); - } -} - -void func_breakable_destroyed(entity this) -{ - func_breakable_look_destroyed(this); - func_breakable_behave_destroyed(this); -} - -void func_breakable_restore(entity this, entity actor, entity trigger) -{ - func_breakable_look_restore(this); - func_breakable_behave_restore(this); -} - -void func_breakable_restore_self(entity this) -{ - func_breakable_restore(this, NULL, NULL); -} - -vector debrisforce; // global, set before calling this -void func_breakable_destroy(entity this, entity actor, entity trigger) -{ - float n, i; - string oldmsg; - - entity act = this.owner; - this.owner = NULL; // set by W_PrepareExplosionByDamage - - // now throw around the debris - n = tokenize_console(this.debris); - for(i = 0; i < n; ++i) - LaunchDebris(this, argv(i), debrisforce); - - func_breakable_destroyed(this); - - if(this.noise) - _sound (this, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM); - - if(this.dmg) - RadiusDamage(this, act, this.dmg, this.dmg_edge, this.dmg_radius, this, NULL, this.dmg_force, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, NULL); - - if(this.cnt) // TODO - __pointparticles(this.cnt, this.absmin * 0.5 + this.absmax * 0.5, '0 0 0', this.count); - - if(this.respawntime) - { - CSQCMODEL_AUTOUPDATE(this); - setthink(this, func_breakable_restore_self); - this.nextthink = time + this.respawntime + crandom() * this.respawntimejitter; - } - - oldmsg = this.message; - this.message = ""; - SUB_UseTargets(this, act, trigger); - this.message = oldmsg; -} - -void func_breakable_destroy_self(entity this) -{ - func_breakable_destroy(this, NULL, NULL); -} - -void func_breakable_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) -{ - if(this.state == STATE_BROKEN) - return; - if(this.spawnflags & NOSPLASH) - if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH)) - return; - if(this.team) - if(attacker.team == this.team) - return; - this.pain_finished = time; - this.health = this.health - damage; - if(this.sprite) - { - WaypointSprite_Ping(this.sprite); - WaypointSprite_UpdateHealth(this.sprite, this.health); - } - func_breakable_colormod(this); - - if(this.health <= 0) - { - debrisforce = force; - - this.takedamage = DAMAGE_NO; - this.event_damage = func_null; - - if(IS_CLIENT(attacker)) //&& this.classname == "func_assault_destructible") - { - this.owner = attacker; - this.realowner = attacker; - } - - // do not explode NOW but in the NEXT FRAME! - // because recursive calls to RadiusDamage are not allowed - this.nextthink = time; - CSQCMODEL_AUTOUPDATE(this); - setthink(this, func_breakable_destroy_self); - } -} - -void func_breakable_reset(entity this) -{ - this.team = this.team_saved; - func_breakable_look_restore(this); - if(this.spawnflags & START_DISABLED) - func_breakable_behave_destroyed(this); - else - func_breakable_behave_restore(this); -} - -// destructible walls that can be used to trigger target_objective_decrease -spawnfunc(func_breakable) -{ - float n, i; - if(!this.health) - this.health = 100; - this.max_health = this.health; - - // yes, I know, MOVETYPE_NONE is not available here, not that one would want it here anyway - if(!this.debrismovetype) this.debrismovetype = MOVETYPE_BOUNCE; - if(!this.debrissolid) this.debrissolid = SOLID_NOT; - if(this.debrisvelocity == '0 0 0') this.debrisvelocity = '0 0 140'; - if(this.debrisvelocityjitter == '0 0 0') this.debrisvelocityjitter = '70 70 70'; - if(this.debrisavelocityjitter == '0 0 0') this.debrisavelocityjitter = '600 600 600'; - if(!this.debristime) this.debristime = 3.5; - if(!this.debristimejitter) this.debristime = 2.5; - - if(this.mdl != "") - this.cnt = _particleeffectnum(this.mdl); - if(this.count == 0) - this.count = 1; - - if(this.message == "") - this.message = "got too close to an explosion"; - if(this.message2 == "") - this.message2 = "was pushed into an explosion by"; - if(!this.dmg_radius) - this.dmg_radius = 150; - if(!this.dmg_force) - this.dmg_force = 200; - - this.mdl = this.model; - SetBrushEntityModel(this); - - if(this.spawnflags & BREAKABLE_NODAMAGE) - this.use = func_breakable_destroy; - else - this.use = func_breakable_restore; - - if(this.spawnflags & BREAKABLE_NODAMAGE) - { - this.takedamage = DAMAGE_NO; - this.event_damage = func_null; - this.bot_attack = false; - } - - // precache all the models - if (this.mdl_dead) - precache_model(this.mdl_dead); - n = tokenize_console(this.debris); - for(i = 0; i < n; ++i) - precache_model(argv(i)); - if(this.noise) - precache_sound(this.noise); - if(this.noise1) - precache_sound(this.noise1); - - this.team_saved = this.team; - IL_PUSH(g_saved_team, this); - this.dropped_origin = this.origin; - - this.reset = func_breakable_reset; - this.reset(this); - - IL_PUSH(g_initforplayer, this); - this.init_for_player = func_breakable_init_for_player; - - CSQCMODEL_AUTOINIT(this); -} - -// for use in maps with a "model" key set -spawnfunc(misc_breakablemodel) { - spawnfunc_func_breakable(this); -} -#endif diff --git a/qcsrc/common/triggers/func/breakable.qh b/qcsrc/common/triggers/func/breakable.qh deleted file mode 100644 index 0efbcfaed..000000000 --- a/qcsrc/common/triggers/func/breakable.qh +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - - -const int BREAKABLE_INDICATE_DAMAGE = BIT(1); -const int BREAKABLE_NODAMAGE = BIT(2); - -const int STATE_ALIVE = 0; -const int STATE_BROKEN = 1; - -#ifdef SVQC -spawnfunc(func_breakable); -#endif diff --git a/qcsrc/common/triggers/func/button.qc b/qcsrc/common/triggers/func/button.qc deleted file mode 100644 index 28e6481c8..000000000 --- a/qcsrc/common/triggers/func/button.qc +++ /dev/null @@ -1,170 +0,0 @@ -#include "button.qh" -#ifdef SVQC -// button and multiple button - -void button_wait(entity this); -void button_return(entity this); - -void button_wait(entity this) -{ - this.state = STATE_TOP; - if(this.wait >= 0) - { - this.nextthink = this.ltime + this.wait; - setthink(this, button_return); - } - SUB_UseTargets(this, this.enemy, NULL); - this.frame = 1; // use alternate textures -} - -void button_done(entity this) -{ - this.state = STATE_BOTTOM; -} - -void button_return(entity this) -{ - this.state = STATE_DOWN; - SUB_CalcMove (this, this.pos1, TSPEED_LINEAR, this.speed, button_done); - this.frame = 0; // use normal textures - if (this.health) - this.takedamage = DAMAGE_YES; // can be shot again -} - - -void button_blocked(entity this, entity blocker) -{ - // do nothing, just don't come all the way back out -} - - -void button_fire(entity this) -{ - this.health = this.max_health; - this.takedamage = DAMAGE_NO; // will be reset upon return - - if (this.state == STATE_UP || this.state == STATE_TOP) - return; - - if (this.noise != "") - _sound (this, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM); - - this.state = STATE_UP; - SUB_CalcMove (this, this.pos2, TSPEED_LINEAR, this.speed, button_wait); -} - -void button_reset(entity this) -{ - this.health = this.max_health; - setorigin(this, this.pos1); - this.frame = 0; // use normal textures - this.state = STATE_BOTTOM; - this.velocity = '0 0 0'; - setthink(this, func_null); - this.nextthink = 0; - if (this.health) - this.takedamage = DAMAGE_YES; // can be shot again -} - -void button_use(entity this, entity actor, entity trigger) -{ - if(this.active != ACTIVE_ACTIVE) - return; - - this.enemy = actor; - button_fire(this); -} - -void button_touch(entity this, entity toucher) -{ - if (!toucher) - return; - if (!toucher.iscreature) - return; - if(toucher.velocity * this.movedir < 0) - return; - this.enemy = toucher; - if (toucher.owner) - this.enemy = toucher.owner; - button_fire (this); -} - -void button_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) -{ - if(this.spawnflags & NOSPLASH) - if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH)) - return; - if (this.spawnflags & BUTTON_DONTACCUMULATEDMG) - { - if (this.health <= damage) - { - this.enemy = attacker; - button_fire(this); - } - } - else - { - this.health = this.health - damage; - if (this.health <= 0) - { - this.enemy = attacker; - button_fire(this); - } - } -} - - -/*QUAKED spawnfunc_func_button (0 .5 .8) ? -When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again. - -"angle" determines the opening direction -"target" all entities with a matching targetname will be used -"speed" override the default 40 speed -"wait" override the default 1 second wait (-1 = never return) -"lip" override the default 4 pixel lip remaining at end of move -"health" if set, the button must be killed instead of touched. If set to -1, the button will fire on ANY attack, even damageless ones like the InstaGib laser -"noise" sound that is played when the button is activated -*/ -spawnfunc(func_button) -{ - SetMovedir(this); - - if (!InitMovingBrushTrigger(this)) - return; - this.effects |= EF_LOWPRECISION; - - setblocked(this, button_blocked); - this.use = button_use; - -// if (this.health == 0) // all buttons are now shootable -// this.health = 10; - if (this.health) - { - this.max_health = this.health; - this.event_damage = button_damage; - this.takedamage = DAMAGE_YES; - } - else - settouch(this, button_touch); - - if (!this.speed) - this.speed = 40; - if (!this.wait) - this.wait = 1; - if (!this.lip) - this.lip = 4; - - if(this.noise != "") - precache_sound(this.noise); - - this.active = ACTIVE_ACTIVE; - - this.pos1 = this.origin; - this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip); - this.flags |= FL_NOTARGET; - - this.reset = button_reset; - - button_reset(this); -} -#endif diff --git a/qcsrc/common/triggers/func/button.qh b/qcsrc/common/triggers/func/button.qh deleted file mode 100644 index 86a0fc918..000000000 --- a/qcsrc/common/triggers/func/button.qh +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - - -const int BUTTON_DONTACCUMULATEDMG = BIT(7); diff --git a/qcsrc/common/triggers/func/conveyor.qc b/qcsrc/common/triggers/func/conveyor.qc deleted file mode 100644 index 9ad326cfa..000000000 --- a/qcsrc/common/triggers/func/conveyor.qc +++ /dev/null @@ -1,178 +0,0 @@ -#include "conveyor.qh" -REGISTER_NET_LINKED(ENT_CLIENT_CONVEYOR) - -void conveyor_think(entity this) -{ -#ifdef CSQC - // TODO: check if this is what is causing the glitchiness when switching between them - float dt = time - this.move_time; - this.move_time = time; - if(dt <= 0) { return; } -#endif - - // set myself as current conveyor where possible - IL_EACH(g_conveyed, it.conveyor == this, - { - it.conveyor = NULL; - IL_REMOVE(g_conveyed, it); - }); - - if(this.active == ACTIVE_ACTIVE) - { - FOREACH_ENTITY_RADIUS((this.absmin + this.absmax) * 0.5, vlen(this.absmax - this.absmin) * 0.5 + 1, it.conveyor.active == ACTIVE_NOT && isPushable(it), - { - vector emin = it.absmin; - vector emax = it.absmax; - if(this.solid == SOLID_BSP) - { - emin -= '1 1 1'; - emax += '1 1 1'; - } - if(boxesoverlap(emin, emax, this.absmin, this.absmax)) // quick - if(WarpZoneLib_BoxTouchesBrush(emin, emax, this, it)) // accurate - { - if(!it.conveyor) - IL_PUSH(g_conveyed, it); - it.conveyor = this; - } - }); - - IL_EACH(g_conveyed, it.conveyor == this, - { - if(IS_CLIENT(it)) // doing it via velocity has quite some advantages - continue; // done in SV_PlayerPhysics continue; - - setorigin(it, it.origin + this.movedir * PHYS_INPUT_FRAMETIME); - move_out_of_solid(it); -#ifdef SVQC - UpdateCSQCProjectile(it); -#endif - /* - // stupid conveyor code - tracebox(it.origin, it.mins, it.maxs, it.origin + this.movedir * sys_frametime, MOVE_NORMAL, it); - if(trace_fraction > 0) - setorigin(it, trace_endpos); - */ - }); - } - -#ifdef SVQC - this.nextthink = time; -#endif -} - -#ifdef SVQC - -bool conveyor_send(entity this, entity to, int sendflags) -{ - WriteHeader(MSG_ENTITY, ENT_CLIENT_CONVEYOR); - WriteByte(MSG_ENTITY, sendflags); - - if(sendflags & SF_TRIGGER_INIT) - { - WriteByte(MSG_ENTITY, this.warpzone_isboxy); - WriteVector(MSG_ENTITY, this.origin); - - WriteVector(MSG_ENTITY, this.mins); - WriteVector(MSG_ENTITY, this.maxs); - - WriteVector(MSG_ENTITY, this.movedir); - - WriteByte(MSG_ENTITY, this.speed); - WriteByte(MSG_ENTITY, this.active); - - WriteString(MSG_ENTITY, this.targetname); - WriteString(MSG_ENTITY, this.target); - } - - if(sendflags & SF_TRIGGER_UPDATE) - WriteByte(MSG_ENTITY, this.active); - - return true; -} - -void conveyor_init(entity this) -{ - if (!this.speed) this.speed = 200; - this.movedir *= this.speed; - setthink(this, conveyor_think); - this.nextthink = time; - this.setactive = generic_netlinked_setactive; - IFTARGETED - { - // backwards compatibility - this.use = generic_netlinked_legacy_use; - } - this.reset = generic_netlinked_reset; - this.reset(this); - - FixSize(this); - - Net_LinkEntity(this, 0, false, conveyor_send); - - this.SendFlags |= SF_TRIGGER_INIT; -} - -spawnfunc(trigger_conveyor) -{ - SetMovedir(this); - EXACTTRIGGER_INIT; - conveyor_init(this); -} - -spawnfunc(func_conveyor) -{ - SetMovedir(this); - InitMovingBrushTrigger(this); - set_movetype(this, MOVETYPE_NONE); - conveyor_init(this); -} - -#elif defined(CSQC) - -void conveyor_draw(entity this) { conveyor_think(this); } - -void conveyor_init(entity this, bool isnew) -{ - if(isnew) - IL_PUSH(g_drawables, this); - this.draw = conveyor_draw; - this.drawmask = MASK_NORMAL; - - set_movetype(this, MOVETYPE_NONE); - this.model = ""; - this.solid = SOLID_TRIGGER; - this.move_time = time; -} - -NET_HANDLE(ENT_CLIENT_CONVEYOR, bool isnew) -{ - int sendflags = ReadByte(); - - if(sendflags & SF_TRIGGER_INIT) - { - this.warpzone_isboxy = ReadByte(); - this.origin = ReadVector(); - setorigin(this, this.origin); - - this.mins = ReadVector(); - this.maxs = ReadVector(); - setsize(this, this.mins, this.maxs); - - this.movedir = ReadVector(); - - this.speed = ReadByte(); - this.active = ReadByte(); - - this.targetname = strzone(ReadString()); - this.target = strzone(ReadString()); - - conveyor_init(this, isnew); - } - - if(sendflags & SF_TRIGGER_UPDATE) - this.active = ReadByte(); - - return true; -} -#endif diff --git a/qcsrc/common/triggers/func/conveyor.qh b/qcsrc/common/triggers/func/conveyor.qh deleted file mode 100644 index 22b674ff3..000000000 --- a/qcsrc/common/triggers/func/conveyor.qh +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once -#include "../defs.qh" - -IntrusiveList g_conveyed; -STATIC_INIT(g_conveyed) { g_conveyed = IL_NEW(); } diff --git a/qcsrc/common/triggers/func/door.qc b/qcsrc/common/triggers/func/door.qc deleted file mode 100644 index c19041aa0..000000000 --- a/qcsrc/common/triggers/func/door.qc +++ /dev/null @@ -1,801 +0,0 @@ -#include "door.qh" -#include "door_rotating.qh" -/* - -Doors are similar to buttons, but can spawn a fat trigger field around them -to open without a touch, and they link together to form simultanious -double/quad doors. - -Door.owner is the master door. If there is only one door, it points to itself. -If multiple doors, all will point to a single one. - -Door.enemy chains from the master door through all doors linked in the chain. - -*/ - - -/* -============================================================================= - -THINK FUNCTIONS - -============================================================================= -*/ - -void door_go_down(entity this); -void door_go_up(entity this, entity actor, entity trigger); - -void door_blocked(entity this, entity blocker) -{ - if((this.spawnflags & DOOR_CRUSH) -#ifdef SVQC - && (blocker.takedamage != DAMAGE_NO) -#elif defined(CSQC) - && !IS_DEAD(blocker) -#endif - ) - { // KIll Kill Kill!! -#ifdef SVQC - Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); -#endif - } - else - { -#ifdef SVQC - if((this.dmg) && (blocker.takedamage == DAMAGE_YES)) // Shall we bite? - Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); -#endif - - // don't change direction for dead or dying stuff - if(IS_DEAD(blocker) -#ifdef SVQC - && (blocker.takedamage == DAMAGE_NO) -#endif - ) - { - if (this.wait >= 0) - { - if (this.state == STATE_DOWN) - { - if (this.classname == "door") - door_go_up(this, NULL, NULL); - else - door_rotating_go_up(this, blocker); - } - else - { - if (this.classname == "door") - door_go_down(this); - else - door_rotating_go_down(this); - } - } - } -#ifdef SVQC - else - { - //gib dying stuff just to make sure - if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) // Shall we bite? - Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); - } -#endif - } -} - -void door_hit_top(entity this) -{ - if (this.noise1 != "") - _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM); - this.state = STATE_TOP; - if (this.spawnflags & DOOR_TOGGLE) - return; // don't come down automatically - if (this.classname == "door") - { - setthink(this, door_go_down); - } else - { - setthink(this, door_rotating_go_down); - } - this.nextthink = this.ltime + this.wait; -} - -void door_hit_bottom(entity this) -{ - if (this.noise1 != "") - _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM); - this.state = STATE_BOTTOM; -} - -void door_go_down(entity this) -{ - if (this.noise2 != "") - _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM); - if (this.max_health) - { - this.takedamage = DAMAGE_YES; - this.health = this.max_health; - } - - this.state = STATE_DOWN; - SUB_CalcMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_hit_bottom); -} - -void door_go_up(entity this, entity actor, entity trigger) -{ - if (this.state == STATE_UP) - return; // already going up - - if (this.state == STATE_TOP) - { // reset top wait time - this.nextthink = this.ltime + this.wait; - return; - } - - if (this.noise2 != "") - _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM); - this.state = STATE_UP; - SUB_CalcMove (this, this.pos2, TSPEED_LINEAR, this.speed, door_hit_top); - - string oldmessage; - oldmessage = this.message; - this.message = ""; - SUB_UseTargets(this, actor, trigger); - this.message = oldmessage; -} - - -/* -============================================================================= - -ACTIVATION FUNCTIONS - -============================================================================= -*/ - -bool door_check_keys(entity door, entity player) -{ - if(door.owner) - door = door.owner; - - // no key needed - if(!door.itemkeys) - return true; - - // this door require a key - // only a player can have a key - if(!IS_PLAYER(player)) - return false; - - entity store = player; -#ifdef SVQC - store = PS(player); -#endif - int valid = (door.itemkeys & store.itemkeys); - door.itemkeys &= ~valid; // only some of the needed keys were given - - if(!door.itemkeys) - { -#ifdef SVQC - play2(player, door.noise); - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_UNLOCKED); -#endif - return true; - } - - if(!valid) - { -#ifdef SVQC - if(player.key_door_messagetime <= time) - { - play2(player, door.noise3); - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys)); - player.key_door_messagetime = time + 2; - } -#endif - return false; - } - - // door needs keys the player doesn't have -#ifdef SVQC - if(player.key_door_messagetime <= time) - { - play2(player, door.noise3); - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys)); - player.key_door_messagetime = time + 2; - } -#endif - - return false; -} - -void door_fire(entity this, entity actor, entity trigger) -{ - if (this.owner != this) - objerror (this, "door_fire: this.owner != this"); - - if (this.spawnflags & DOOR_TOGGLE) - { - if (this.state == STATE_UP || this.state == STATE_TOP) - { - entity e = this; - do { - if (e.classname == "door") { - door_go_down(e); - } else { - door_rotating_go_down(e); - } - e = e.enemy; - } while ((e != this) && (e != NULL)); - return; - } - } - -// trigger all paired doors - entity e = this; - do { - if (e.classname == "door") { - door_go_up(e, actor, trigger); - } else { - // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction - if ((e.spawnflags & DOOR_ROTATING_BIDIR) && trigger.trigger_reverse!=0 && e.lip != 666 && e.state == STATE_BOTTOM) { - e.lip = 666; // e.lip is used to remember reverse opening direction for door_rotating - e.pos2 = '0 0 0' - e.pos2; - } - // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side - if (!((e.spawnflags & DOOR_ROTATING_BIDIR) && (e.spawnflags & DOOR_ROTATING_BIDIR_IN_DOWN) && e.state == STATE_DOWN - && (((e.lip == 666) && (trigger.trigger_reverse == 0)) || ((e.lip != 666) && (trigger.trigger_reverse != 0))))) - { - door_rotating_go_up(e, trigger); - } - } - e = e.enemy; - } while ((e != this) && (e != NULL)); -} - -void door_use(entity this, entity actor, entity trigger) -{ - //dprint("door_use (model: ");dprint(this.model);dprint(")\n"); - - if (this.owner) - door_fire(this.owner, actor, trigger); -} - -void door_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) -{ - if(this.spawnflags & NOSPLASH) - if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH)) - return; - this.health = this.health - damage; - - if (this.itemkeys) - { - // don't allow opening doors through damage if keys are required - return; - } - - if (this.health <= 0) - { - this.owner.health = this.owner.max_health; - this.owner.takedamage = DAMAGE_NO; // wil be reset upon return - door_use(this.owner, NULL, NULL); - } -} - -.float door_finished; - -/* -================ -door_touch - -Prints messages -================ -*/ - -void door_touch(entity this, entity toucher) -{ - if (!IS_PLAYER(toucher)) - return; - if (this.owner.door_finished > time) - return; - - this.owner.door_finished = time + 2; - -#ifdef SVQC - if (!(this.owner.dmg) && (this.owner.message != "")) - { - if (IS_CLIENT(toucher)) - centerprint(toucher, this.owner.message); - play2(toucher, this.owner.noise); - } -#endif -} - -void door_generic_plat_blocked(entity this, entity blocker) -{ - if((this.spawnflags & DOOR_CRUSH) && (blocker.takedamage != DAMAGE_NO)) { // Kill Kill Kill!! -#ifdef SVQC - Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); -#endif - } - else - { - -#ifdef SVQC - if((this.dmg) && (blocker.takedamage == DAMAGE_YES)) // Shall we bite? - Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); -#endif - - //Dont chamge direction for dead or dying stuff - if(IS_DEAD(blocker) && (blocker.takedamage == DAMAGE_NO)) - { - if (this.wait >= 0) - { - if (this.state == STATE_DOWN) - door_rotating_go_up (this, blocker); - else - door_rotating_go_down (this); - } - } -#ifdef SVQC - else - { - //gib dying stuff just to make sure - if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) // Shall we bite? - Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); - } -#endif - } -} - -/* -========================================= -door trigger - -Spawned if a door lacks a real activator -========================================= -*/ - -void door_trigger_touch(entity this, entity toucher) -{ - if (toucher.health < 1) -#ifdef SVQC - if (!((toucher.iscreature || (toucher.flags & FL_PROJECTILE)) && !IS_DEAD(toucher))) -#elif defined(CSQC) - if(!((IS_CLIENT(toucher) || toucher.classname == "csqcprojectile") && !IS_DEAD(toucher))) -#endif - return; - - if (time < this.door_finished) - return; - - // check if door is locked - if (!door_check_keys(this, toucher)) - return; - - this.door_finished = time + 1; - - door_use(this.owner, toucher, NULL); -} - -void door_spawnfield(entity this, vector fmins, vector fmaxs) -{ - entity trigger; - vector t1 = fmins, t2 = fmaxs; - - trigger = new(doortriggerfield); - set_movetype(trigger, MOVETYPE_NONE); - trigger.solid = SOLID_TRIGGER; - trigger.owner = this; -#ifdef SVQC - settouch(trigger, door_trigger_touch); -#endif - - setsize (trigger, t1 - '60 60 8', t2 + '60 60 8'); -} - - -/* -============= -LinkDoors - - -============= -*/ - -entity LinkDoors_nextent(entity cur, entity near, entity pass) -{ - while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & DOOR_DONT_LINK) || cur.enemy)) - { - } - return cur; -} - -bool LinkDoors_isconnected(entity e1, entity e2, entity pass) -{ - float DELTA = 4; - if((e1.absmin_x > e2.absmax_x + DELTA) - || (e1.absmin_y > e2.absmax_y + DELTA) - || (e1.absmin_z > e2.absmax_z + DELTA) - || (e2.absmin_x > e1.absmax_x + DELTA) - || (e2.absmin_y > e1.absmax_y + DELTA) - || (e2.absmin_z > e1.absmax_z + DELTA) - ) { return false; } - return true; -} - -#ifdef SVQC -void door_link(); -#endif -void LinkDoors(entity this) -{ - entity t; - vector cmins, cmaxs; - -#ifdef SVQC - door_link(); -#endif - - if (this.enemy) - return; // already linked by another door - if (this.spawnflags & DOOR_DONT_LINK) - { - this.owner = this.enemy = this; - - if (this.health) - return; - IFTARGETED - return; - if (this.items) - return; - - door_spawnfield(this, this.absmin, this.absmax); - - return; // don't want to link this door - } - - FindConnectedComponent(this, enemy, LinkDoors_nextent, LinkDoors_isconnected, this); - - // set owner, and make a loop of the chain - LOG_TRACE("LinkDoors: linking doors:"); - for(t = this; ; t = t.enemy) - { - LOG_TRACE(" ", etos(t)); - t.owner = this; - if(t.enemy == NULL) - { - t.enemy = this; - break; - } - } - LOG_TRACE(""); - - // collect health, targetname, message, size - cmins = this.absmin; - cmaxs = this.absmax; - for(t = this; ; t = t.enemy) - { - if(t.health && !this.health) - this.health = t.health; - if((t.targetname != "") && (this.targetname == "")) - this.targetname = t.targetname; - if((t.message != "") && (this.message == "")) - this.message = t.message; - if (t.absmin_x < cmins_x) - cmins_x = t.absmin_x; - if (t.absmin_y < cmins_y) - cmins_y = t.absmin_y; - if (t.absmin_z < cmins_z) - cmins_z = t.absmin_z; - if (t.absmax_x > cmaxs_x) - cmaxs_x = t.absmax_x; - if (t.absmax_y > cmaxs_y) - cmaxs_y = t.absmax_y; - if (t.absmax_z > cmaxs_z) - cmaxs_z = t.absmax_z; - if(t.enemy == this) - break; - } - - // distribute health, targetname, message - for(t = this; t; t = t.enemy) - { - t.health = this.health; - t.targetname = this.targetname; - t.message = this.message; - if(t.enemy == this) - break; - } - - // shootable, or triggered doors just needed the owner/enemy links, - // they don't spawn a field - - if (this.health) - return; - IFTARGETED - return; - if (this.items) - return; - - door_spawnfield(this, cmins, cmaxs); -} - -REGISTER_NET_LINKED(ENT_CLIENT_DOOR) - -#ifdef SVQC -/*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE -if two doors touch, they are assumed to be connected and operate as a unit. - -TOGGLE causes the door to wait in both the start and end states for a trigger event. - -START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). - -GOLD_KEY causes the door to open only if the activator holds a gold key. - -SILVER_KEY causes the door to open only if the activator holds a silver key. - -"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet -"angle" determines the opening direction -"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. -"health" if set, door must be shot open -"speed" movement speed (100 default) -"wait" wait before returning (3 default, -1 = never return) -"lip" lip remaining at end of move (8 default) -"dmg" damage to inflict when blocked (2 default) -"sounds" -0) no sound -1) stone -2) base -3) stone chain -4) screechy metal -FIXME: only one sound set available at the time being - -*/ - -float door_send(entity this, entity to, float sf) -{ - WriteHeader(MSG_ENTITY, ENT_CLIENT_DOOR); - WriteByte(MSG_ENTITY, sf); - - if(sf & SF_TRIGGER_INIT) - { - WriteString(MSG_ENTITY, this.classname); - WriteByte(MSG_ENTITY, this.spawnflags); - - WriteString(MSG_ENTITY, this.model); - - trigger_common_write(this, true); - - WriteVector(MSG_ENTITY, this.pos1); - WriteVector(MSG_ENTITY, this.pos2); - - WriteVector(MSG_ENTITY, this.size); - - WriteShort(MSG_ENTITY, this.wait); - WriteShort(MSG_ENTITY, this.speed); - WriteByte(MSG_ENTITY, this.lip); - WriteByte(MSG_ENTITY, this.state); - WriteCoord(MSG_ENTITY, this.ltime); - } - - if(sf & SF_TRIGGER_RESET) - { - // client makes use of this, we do not - } - - if(sf & SF_TRIGGER_UPDATE) - { - WriteVector(MSG_ENTITY, this.origin); - - WriteVector(MSG_ENTITY, this.pos1); - WriteVector(MSG_ENTITY, this.pos2); - } - - return true; -} - -void door_link() -{ - // set size now, as everything is loaded - //FixSize(this); - //Net_LinkEntity(this, false, 0, door_send); -} -#endif - -void door_init_startopen(entity this) -{ - setorigin(this, this.pos2); - this.pos2 = this.pos1; - this.pos1 = this.origin; - -#ifdef SVQC - this.SendFlags |= SF_TRIGGER_UPDATE; -#endif -} - -void door_reset(entity this) -{ - setorigin(this, this.pos1); - this.velocity = '0 0 0'; - this.state = STATE_BOTTOM; - setthink(this, func_null); - this.nextthink = 0; - -#ifdef SVQC - this.SendFlags |= SF_TRIGGER_RESET; -#endif -} - -#ifdef SVQC - -// common code for func_door and func_door_rotating spawnfuncs -void door_init_shared(entity this) -{ - this.max_health = this.health; - - // unlock sound - if(this.noise == "") - { - this.noise = "misc/talk.wav"; - } - // door still locked sound - if(this.noise3 == "") - { - this.noise3 = "misc/talk.wav"; - } - precache_sound(this.noise); - precache_sound(this.noise3); - - if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message == "")) - { - this.message = "was squished"; - } - if((this.dmg || (this.spawnflags & DOOR_CRUSH)) && (this.message2 == "")) - { - this.message2 = "was squished by"; - } - - // TODO: other soundpacks - if (this.sounds > 0) - { - this.noise2 = "plats/medplat1.wav"; - this.noise1 = "plats/medplat2.wav"; - } - - // sound when door stops moving - if(this.noise1 && this.noise1 != "") - { - precache_sound(this.noise1); - } - // sound when door is moving - if(this.noise2 && this.noise2 != "") - { - precache_sound(this.noise2); - } - - if (!this.wait) - { - this.wait = 3; - } - if (!this.lip) - { - this.lip = 8; - } - - this.state = STATE_BOTTOM; - - if (this.health) - { - //this.canteamdamage = true; // TODO - this.takedamage = DAMAGE_YES; - this.event_damage = door_damage; - } - - if (this.items) - { - this.wait = -1; - } -} - -// spawnflags require key (for now only func_door) -spawnfunc(func_door) -{ - // Quake 1 keys compatibility - if (this.spawnflags & SPAWNFLAGS_GOLD_KEY) - this.itemkeys |= ITEM_KEY_BIT(0); - if (this.spawnflags & SPAWNFLAGS_SILVER_KEY) - this.itemkeys |= ITEM_KEY_BIT(1); - - SetMovedir(this); - - if (!InitMovingBrushTrigger(this)) - return; - this.effects |= EF_LOWPRECISION; - this.classname = "door"; - - setblocked(this, door_blocked); - this.use = door_use; - - this.pos1 = this.origin; - this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip); - - if(this.spawnflags & DOOR_NONSOLID) - this.solid = SOLID_NOT; - -// DOOR_START_OPEN is to allow an entity to be lighted in the closed position -// but spawn in the open position - if (this.spawnflags & DOOR_START_OPEN) - InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION); - - door_init_shared(this); - - if (!this.speed) - { - this.speed = 100; - } - - settouch(this, door_touch); - -// LinkDoors can't be done until all of the doors have been spawned, so -// the sizes can be detected properly. - InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS); - - this.reset = door_reset; -} - -#elif defined(CSQC) - -NET_HANDLE(ENT_CLIENT_DOOR, bool isnew) -{ - int sf = ReadByte(); - - if(sf & SF_TRIGGER_INIT) - { - this.classname = strzone(ReadString()); - this.spawnflags = ReadByte(); - - this.mdl = strzone(ReadString()); - _setmodel(this, this.mdl); - - trigger_common_read(this, true); - - this.pos1 = ReadVector(); - this.pos2 = ReadVector(); - - this.size = ReadVector(); - - this.wait = ReadShort(); - this.speed = ReadShort(); - this.lip = ReadByte(); - this.state = ReadByte(); - this.ltime = ReadCoord(); - - this.solid = SOLID_BSP; - set_movetype(this, MOVETYPE_PUSH); - this.use = door_use; - - LinkDoors(this); - - if(this.spawnflags & DOOR_START_OPEN) - door_init_startopen(this); - - this.move_time = time; - set_movetype(this, MOVETYPE_PUSH); - } - - if(sf & SF_TRIGGER_RESET) - { - door_reset(this); - } - - if(sf & SF_TRIGGER_UPDATE) - { - this.origin = ReadVector(); - setorigin(this, this.origin); - - this.pos1 = ReadVector(); - this.pos2 = ReadVector(); - } - return true; -} - -#endif diff --git a/qcsrc/common/triggers/func/door.qh b/qcsrc/common/triggers/func/door.qh deleted file mode 100644 index 181de8b7b..000000000 --- a/qcsrc/common/triggers/func/door.qh +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - - -const int DOOR_START_OPEN = BIT(0); -const int DOOR_DONT_LINK = BIT(2); -const int SPAWNFLAGS_GOLD_KEY = BIT(3); // Quake 1 compat, can only be used with func_door! -const int SPAWNFLAGS_SILVER_KEY = BIT(4); // Quake 1 compat, can only be used with func_door! -const int DOOR_TOGGLE = BIT(5); - -const int DOOR_NONSOLID = BIT(10); -const int DOOR_CRUSH = BIT(11); // can't use CRUSH cause that is the same as DOOR_DONT_LINK - - -#ifdef CSQC -// stuff for preload - -.float door_finished; -#endif diff --git a/qcsrc/common/triggers/func/door_rotating.qc b/qcsrc/common/triggers/func/door_rotating.qc deleted file mode 100644 index 41fd05e57..000000000 --- a/qcsrc/common/triggers/func/door_rotating.qc +++ /dev/null @@ -1,159 +0,0 @@ -#include "door_rotating.qh" -/*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS -if two doors touch, they are assumed to be connected and operate as a unit. - -TOGGLE causes the door to wait in both the start and end states for a trigger event. - -BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor. -The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction -must have set trigger_reverse to 1. -BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side. - -START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors). - -"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet -"angle" determines the destination angle for opening. negative values reverse the direction. -"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. -"health" if set, door must be shot open -"speed" movement speed (100 default) -"wait" wait before returning (3 default, -1 = never return) -"dmg" damage to inflict when blocked (2 default) -"sounds" -0) no sound -1) stone -2) base -3) stone chain -4) screechy metal -FIXME: only one sound set available at the time being -*/ - -#ifdef GAMEQC -void door_rotating_hit_top(entity this) -{ - if (this.noise1 != "") - _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM); - this.state = STATE_TOP; - if (this.spawnflags & DOOR_TOGGLE) - return; // don't come down automatically - setthink(this, door_rotating_go_down); - this.nextthink = this.ltime + this.wait; -} - -void door_rotating_hit_bottom(entity this) -{ - if (this.noise1 != "") - _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM); - if (this.lip==666) // this.lip is used to remember reverse opening direction for door_rotating - { - this.pos2 = '0 0 0' - this.pos2; - this.lip = 0; - } - this.state = STATE_BOTTOM; -} - -void door_rotating_go_down(entity this) -{ - if (this.noise2 != "") - _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM); - if (this.max_health) - { - this.takedamage = DAMAGE_YES; - this.health = this.max_health; - } - - this.state = STATE_DOWN; - SUB_CalcAngleMove (this, this.pos1, TSPEED_LINEAR, this.speed, door_rotating_hit_bottom); -} - -void door_rotating_go_up(entity this, entity oth) -{ - if (this.state == STATE_UP) - return; // already going up - - if (this.state == STATE_TOP) - { // reset top wait time - this.nextthink = this.ltime + this.wait; - return; - } - if (this.noise2 != "") - _sound (this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM); - this.state = STATE_UP; - SUB_CalcAngleMove (this, this.pos2, TSPEED_LINEAR, this.speed, door_rotating_hit_top); - - string oldmessage; - oldmessage = this.message; - this.message = ""; - SUB_UseTargets(this, NULL, oth); // TODO: is oth needed here? - this.message = oldmessage; -} -#endif - -#ifdef SVQC -void door_rotating_reset(entity this) -{ - this.angles = this.pos1; - this.avelocity = '0 0 0'; - this.state = STATE_BOTTOM; - setthink(this, func_null); - this.nextthink = 0; -} - -void door_rotating_init_startopen(entity this) -{ - this.angles = this.movedir; - this.pos2 = '0 0 0'; - this.pos1 = this.movedir; -} - -spawnfunc(func_door_rotating) -{ - //if (!this.deathtype) // map makers can override this - // this.deathtype = " got in the way"; - - // I abuse "movedir" for denoting the axis for now - if (this.spawnflags & DOOR_ROTATING_XAXIS) - this.movedir = '0 0 1'; - else if (this.spawnflags & DOOR_ROTATING_YAXIS) - this.movedir = '1 0 0'; - else // Z - this.movedir = '0 1 0'; - - if (this.angles_y==0) this.angles_y = 90; - - this.movedir = this.movedir * this.angles_y; - this.angles = '0 0 0'; - - this.avelocity = this.movedir; - if (!InitMovingBrushTrigger(this)) - return; - this.velocity = '0 0 0'; - //this.effects |= EF_LOWPRECISION; - this.classname = "door_rotating"; - - setblocked(this, door_blocked); - this.use = door_use; - - this.pos1 = '0 0 0'; - this.pos2 = this.movedir; - -// DOOR_START_OPEN is to allow an entity to be lighted in the closed position -// but spawn in the open position - if (this.spawnflags & DOOR_START_OPEN) - InitializeEntity(this, door_rotating_init_startopen, INITPRIO_SETLOCATION); - - door_init_shared(this); - if (!this.speed) - { - this.speed = 50; - } - this.lip = 0; // this.lip is used to remember reverse opening direction for door_rotating - - settouch(this, door_touch); - -// LinkDoors can't be done until all of the doors have been spawned, so -// the sizes can be detected properly. - InitializeEntity(this, LinkDoors, INITPRIO_LINKDOORS); - - this.reset = door_rotating_reset; -} -#endif diff --git a/qcsrc/common/triggers/func/door_rotating.qh b/qcsrc/common/triggers/func/door_rotating.qh deleted file mode 100644 index 5ff572859..000000000 --- a/qcsrc/common/triggers/func/door_rotating.qh +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - - -const int DOOR_ROTATING_BIDIR = BIT(1); -const int DOOR_ROTATING_BIDIR_IN_DOWN = BIT(3); - -const int DOOR_ROTATING_XAXIS = BIT(6); -const int DOOR_ROTATING_YAXIS = BIT(7); - - -#ifdef GAMEQC -void door_rotating_go_down(entity this); -void door_rotating_go_up(entity this, entity oth); -#endif diff --git a/qcsrc/common/triggers/func/door_secret.qc b/qcsrc/common/triggers/func/door_secret.qc deleted file mode 100644 index 78e0dd64e..000000000 --- a/qcsrc/common/triggers/func/door_secret.qc +++ /dev/null @@ -1,266 +0,0 @@ -#include "door_secret.qh" -#ifdef SVQC -void fd_secret_move1(entity this); -void fd_secret_move2(entity this); -void fd_secret_move3(entity this); -void fd_secret_move4(entity this); -void fd_secret_move5(entity this); -void fd_secret_move6(entity this); -void fd_secret_done(entity this); - -void fd_secret_use(entity this, entity actor, entity trigger) -{ - float temp; - string message_save; - - this.health = 10000; - if(!this.bot_attack) - IL_PUSH(g_bot_targets, this); - this.bot_attack = true; - - // exit if still moving around... - if (this.origin != this.oldorigin) - return; - - message_save = this.message; - this.message = ""; // no more message - SUB_UseTargets(this, actor, trigger); // fire all targets / killtargets - this.message = message_save; - - this.velocity = '0 0 0'; - - // Make a sound, wait a little... - - if (this.noise1 != "") - _sound(this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM); - this.nextthink = this.ltime + 0.1; - - temp = 1 - (this.spawnflags & DOOR_SECRET_1ST_LEFT); // 1 or -1 - makevectors(this.mangle); - - if (!this.t_width) - { - if (this.spawnflags & DOOR_SECRET_1ST_DOWN) - this.t_width = fabs(v_up * this.size); - else - this.t_width = fabs(v_right * this.size); - } - - if (!this.t_length) - this.t_length = fabs(v_forward * this.size); - - if (this.spawnflags & DOOR_SECRET_1ST_DOWN) - this.dest1 = this.origin - v_up * this.t_width; - else - this.dest1 = this.origin + v_right * (this.t_width * temp); - - this.dest2 = this.dest1 + v_forward * this.t_length; - SUB_CalcMove(this, this.dest1, TSPEED_LINEAR, this.speed, fd_secret_move1); - if (this.noise2 != "") - _sound(this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM); -} - -void fd_secret_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) -{ - fd_secret_use(this, NULL, NULL); -} - -// Wait after first movement... -void fd_secret_move1(entity this) -{ - this.nextthink = this.ltime + 1.0; - setthink(this, fd_secret_move2); - if (this.noise3 != "") - _sound(this, CH_TRIGGER_SINGLE, this.noise3, VOL_BASE, ATTEN_NORM); -} - -// Start moving sideways w/sound... -void fd_secret_move2(entity this) -{ - if (this.noise2 != "") - _sound(this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM); - SUB_CalcMove(this, this.dest2, TSPEED_LINEAR, this.speed, fd_secret_move3); -} - -// Wait here until time to go back... -void fd_secret_move3(entity this) -{ - if (this.noise3 != "") - _sound(this, CH_TRIGGER_SINGLE, this.noise3, VOL_BASE, ATTEN_NORM); - if (!(this.spawnflags & DOOR_SECRET_OPEN_ONCE) && this.wait >= 0) - { - this.nextthink = this.ltime + this.wait; - setthink(this, fd_secret_move4); - } -} - -// Move backward... -void fd_secret_move4(entity this) -{ - if (this.noise2 != "") - _sound(this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM); - SUB_CalcMove(this, this.dest1, TSPEED_LINEAR, this.speed, fd_secret_move5); -} - -// Wait 1 second... -void fd_secret_move5(entity this) -{ - this.nextthink = this.ltime + 1.0; - setthink(this, fd_secret_move6); - if (this.noise3 != "") - _sound(this, CH_TRIGGER_SINGLE, this.noise3, VOL_BASE, ATTEN_NORM); -} - -void fd_secret_move6(entity this) -{ - if (this.noise2 != "") - _sound(this, CH_TRIGGER_SINGLE, this.noise2, VOL_BASE, ATTEN_NORM); - SUB_CalcMove(this, this.oldorigin, TSPEED_LINEAR, this.speed, fd_secret_done); -} - -void fd_secret_done(entity this) -{ - if (this.spawnflags&DOOR_SECRET_YES_SHOOT) - { - this.health = 10000; - this.takedamage = DAMAGE_YES; - //this.th_pain = fd_secret_use; - } - if (this.noise3 != "") - _sound(this, CH_TRIGGER_SINGLE, this.noise3, VOL_BASE, ATTEN_NORM); -} - -.float door_finished; - -void secret_blocked(entity this, entity blocker) -{ - if (time < this.door_finished) - return; - this.door_finished = time + 0.5; - //T_Damage (other, this, this, this.dmg, this.dmg, this.deathtype, DT_IMPACT, (this.absmin + this.absmax) * 0.5, '0 0 0', Obituary_Generic); -} - -/* -============== -secret_touch - -Prints messages -================ -*/ -void secret_touch(entity this, entity toucher) -{ - if (!toucher.iscreature) - return; - if (this.door_finished > time) - return; - - this.door_finished = time + 2; - - if (this.message) - { - if (IS_CLIENT(toucher)) - centerprint(toucher, this.message); - play2(toucher, this.noise); - } -} - -void secret_reset(entity this) -{ - if (this.spawnflags & DOOR_SECRET_YES_SHOOT) - { - this.health = 10000; - this.takedamage = DAMAGE_YES; - } - setorigin(this, this.oldorigin); - setthink(this, func_null); - this.nextthink = 0; -} - -/*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot -Basic secret door. Slides back, then to the side. Angle determines direction. -wait = # of seconds before coming back -1st_left = 1st move is left of arrow -1st_down = 1st move is down from arrow -always_shoot = even if targeted, keep shootable -t_width = override WIDTH to move back (or height if going down) -t_length = override LENGTH to move sideways -"dmg" damage to inflict when blocked (2 default) - -If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage. -"sounds" -1) medieval -2) metal -3) base -*/ - -spawnfunc(func_door_secret) -{ - /*if (!this.deathtype) // map makers can override this - this.deathtype = " got in the way";*/ - - if (!this.dmg) - { - this.dmg = 2; - } - - // Magic formula... - this.mangle = this.angles; - this.angles = '0 0 0'; - this.classname = "door"; - if (!InitMovingBrushTrigger(this)) return; - this.effects |= EF_LOWPRECISION; - - // TODO: other soundpacks - if (this.sounds > 0) - { - this.noise1 = "plats/medplat1.wav"; - this.noise2 = "plats/medplat1.wav"; - this.noise3 = "plats/medplat2.wav"; - } - - // sound on touch - if (this.noise == "") - { - this.noise = "misc/talk.wav"; - } - precache_sound(this.noise); - // sound while moving backwards - if (this.noise1 && this.noise1 != "") - { - precache_sound(this.noise1); - } - // sound while moving sideways - if (this.noise2 && this.noise2 != "") - { - precache_sound(this.noise2); - } - // sound when door stops moving - if (this.noise3 && this.noise3 != "") - { - precache_sound(this.noise3); - } - - settouch(this, secret_touch); - setblocked(this, secret_blocked); - this.speed = 50; - this.use = fd_secret_use; - IFTARGETED - { - } - else - this.spawnflags |= DOOR_SECRET_YES_SHOOT; - - if (this.spawnflags & DOOR_SECRET_YES_SHOOT) - { - //this.canteamdamage = true; // TODO - this.health = 10000; - this.takedamage = DAMAGE_YES; - this.event_damage = fd_secret_damage; - } - this.oldorigin = this.origin; - if (!this.wait) this.wait = 5; // seconds before closing - - this.reset = secret_reset; - this.reset(this); -} -#endif diff --git a/qcsrc/common/triggers/func/door_secret.qh b/qcsrc/common/triggers/func/door_secret.qh deleted file mode 100644 index ee575bc42..000000000 --- a/qcsrc/common/triggers/func/door_secret.qh +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - - -const int DOOR_SECRET_OPEN_ONCE = BIT(0); // stays open - LEGACY, set wait to -1 instead -const int DOOR_SECRET_1ST_LEFT = BIT(1); // 1st move is left of arrow -const int DOOR_SECRET_1ST_DOWN = BIT(2); // 1st move is down from arrow -const int DOOR_SECRET_NO_SHOOT = BIT(3); // only opened by trigger -const int DOOR_SECRET_YES_SHOOT = BIT(4); // shootable even if targeted diff --git a/qcsrc/common/triggers/func/fourier.qc b/qcsrc/common/triggers/func/fourier.qc deleted file mode 100644 index 28e0f0f7c..000000000 --- a/qcsrc/common/triggers/func/fourier.qc +++ /dev/null @@ -1,89 +0,0 @@ -#include "fourier.qh" -#ifdef SVQC -/*QUAKED spawnfunc_func_fourier (0 .5 .8) ? -Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions. -netname: list of quadruples, separated by spaces; note that phase 0 represents a sine wave, and phase 0.25 a cosine wave (by default, it uses 1 0 0 0 1, to match func_bobbing's defaults -speed: how long one cycle of frequency multiplier 1 in seconds (default 4) -height: amplitude modifier (default 32) -phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0) -noise: path/name of looping .wav file to play. -dmg: Do this mutch dmg every .dmgtime intervall when blocked -dmgtime: See above. -*/ - -void func_fourier_controller_think(entity this) -{ - vector v; - float n, i, t; - - this.nextthink = time + 0.1; - if(this.owner.active != ACTIVE_ACTIVE) - { - this.owner.velocity = '0 0 0'; - return; - } - - - n = floor((tokenize_console(this.owner.netname)) / 5); - t = this.nextthink * this.owner.cnt + this.owner.phase * 360; - - v = this.owner.destvec; - - for(i = 0; i < n; ++i) - { - makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0'); - v = v + ('1 0 0' * stof(argv(i*5+2)) + '0 1 0' * stof(argv(i*5+3)) + '0 0 1' * stof(argv(i*5+4))) * this.owner.height * v_forward_y; - } - - if(this.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed - // * 10 so it will arrive in 0.1 sec - this.owner.velocity = (v - this.owner.origin) * 10; -} - -spawnfunc(func_fourier) -{ - entity controller; - if (this.noise != "") - { - precache_sound(this.noise); - soundto(MSG_INIT, this, CH_TRIGGER_SINGLE, this.noise, VOL_BASE, ATTEN_IDLE); - } - - if (!this.speed) - this.speed = 4; - if (!this.height) - this.height = 32; - this.destvec = this.origin; - this.cnt = 360 / this.speed; - - setblocked(this, generic_plat_blocked); - if(this.dmg && (this.message == "")) - this.message = " was squished"; - if(this.dmg && (this.message2 == "")) - this.message2 = "was squished by"; - if(this.dmg && (!this.dmgtime)) - this.dmgtime = 0.25; - this.dmgtime2 = time; - - if(this.netname == "") - this.netname = "1 0 0 0 1"; - - if (!InitMovingBrushTrigger(this)) - return; - - this.active = ACTIVE_ACTIVE; - - // wait for targets to spawn - controller = new(func_fourier_controller); - controller.owner = this; - controller.nextthink = time + 1; - setthink(controller, func_fourier_controller_think); - this.nextthink = this.ltime + 999999999; - setthink(this, SUB_NullThink); // for PushMove - - // Savage: Reduce bandwith, critical on e.g. nexdm02 - this.effects |= EF_LOWPRECISION; - - // TODO make a reset function for this one -} -#endif diff --git a/qcsrc/common/triggers/func/fourier.qh b/qcsrc/common/triggers/func/fourier.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/func/fourier.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/func/include.qc b/qcsrc/common/triggers/func/include.qc deleted file mode 100644 index 290c2e984..000000000 --- a/qcsrc/common/triggers/func/include.qc +++ /dev/null @@ -1,19 +0,0 @@ -#include "include.qh" - -#include "bobbing.qc" -#include "breakable.qc" -#include "button.qc" -#include "conveyor.qc" -#include "door.qc" -#include "door_rotating.qc" -#include "door_secret.qc" -#include "fourier.qc" -#include "ladder.qc" -#include "pendulum.qc" -#include "plat.qc" -#include "pointparticles.qc" -#include "rainsnow.qc" -#include "rotating.qc" -#include "stardust.qc" -#include "train.qc" -#include "vectormamamam.qc" diff --git a/qcsrc/common/triggers/func/include.qh b/qcsrc/common/triggers/func/include.qh deleted file mode 100644 index 4e8b94cd1..000000000 --- a/qcsrc/common/triggers/func/include.qh +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include "door.qh" -#include "ladder.qh" -#include "train.qh" diff --git a/qcsrc/common/triggers/func/ladder.qc b/qcsrc/common/triggers/func/ladder.qc deleted file mode 100644 index 020ecca08..000000000 --- a/qcsrc/common/triggers/func/ladder.qc +++ /dev/null @@ -1,148 +0,0 @@ -#include "ladder.qh" -REGISTER_NET_LINKED(ENT_CLIENT_LADDER) - -void func_ladder_touch(entity this, entity toucher) -{ -#ifdef SVQC - if (!toucher.iscreature) - return; - if(IS_VEHICLE(toucher)) - return; -#elif defined(CSQC) - if(!toucher.isplayermodel) - return; -#endif - - EXACTTRIGGER_TOUCH(this, toucher); - - toucher.ladder_time = time + 0.1; - toucher.ladder_entity = this; -} - -#ifdef SVQC -bool func_ladder_send(entity this, entity to, int sf) -{ - WriteHeader(MSG_ENTITY, ENT_CLIENT_LADDER); - - WriteString(MSG_ENTITY, this.classname); - WriteByte(MSG_ENTITY, this.skin); - WriteCoord(MSG_ENTITY, this.speed); - - trigger_common_write(this, false); - - return true; -} - -void func_ladder_link(entity this) -{ - trigger_link(this, func_ladder_send); - //this.model = "null"; -} - -void func_ladder_init(entity this) -{ - settouch(this, func_ladder_touch); - trigger_init(this); - func_ladder_link(this); - - if(min(this.absmax.x - this.absmin.x, this.absmax.y - this.absmin.y) > 100) - return; - - entity tracetest_ent = spawn(); - setsize(tracetest_ent, PL_MIN_CONST, PL_MAX_CONST); - tracetest_ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; - - vector top_min = (this.absmin + this.absmax) / 2; - top_min.z = this.absmax.z; - vector top_max = top_min; - top_max.z += PL_MAX_CONST.z - PL_MIN_CONST.z; - tracebox(top_max + jumpstepheightvec, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent); - if(trace_startsolid) - { - tracebox(top_max + stepheightvec, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent); - if(trace_startsolid) - { - tracebox(top_max, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent); - if(trace_startsolid) - { - if(this.absmax.x - this.absmin.x > PL_MAX_CONST.x - PL_MIN_CONST.x - && this.absmax.y - this.absmin.y < this.absmax.x - this.absmin.x) - { - // move top on one side - top_max.y = top_min.y = this.absmin.y + (PL_MAX_CONST.y - PL_MIN_CONST.y) * 0.75; - } - else if(this.absmax.y - this.absmin.y > PL_MAX_CONST.y - PL_MIN_CONST.y - && this.absmax.x - this.absmin.x < this.absmax.y - this.absmin.y) - { - // move top on one side - top_max.x = top_min.x = this.absmin.x + (PL_MAX_CONST.x - PL_MIN_CONST.x) * 0.75; - } - tracebox(top_max, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent); - if(trace_startsolid) - { - if(this.absmax.x - this.absmin.x > PL_MAX_CONST.x - PL_MIN_CONST.x - && this.absmax.y - this.absmin.y < this.absmax.x - this.absmin.x) - { - // alternatively on the other side - top_max.y = top_min.y = this.absmax.y - (PL_MAX_CONST.y - PL_MIN_CONST.y) * 0.75; - } - else if(this.absmax.y - this.absmin.y > PL_MAX_CONST.y - PL_MIN_CONST.y - && this.absmax.x - this.absmin.x < this.absmax.y - this.absmin.y) - { - // alternatively on the other side - top_max.x = top_min.x = this.absmax.x - (PL_MAX_CONST.x - PL_MIN_CONST.x) * 0.75; - } - tracebox(top_max, PL_MIN_CONST, PL_MAX_CONST, top_min, MOVE_NOMONSTERS, tracetest_ent); - } - } - } - } - if(trace_startsolid || trace_endpos.z < this.absmax.z) - { - delete(tracetest_ent); - return; - } - - this.bot_pickup = true; // allow bots to make use of this ladder - float cost = waypoint_getlinearcost(trace_endpos.z - this.absmin.z); - top_min = trace_endpos; - waypoint_spawnforteleporter_boxes(this, WAYPOINTFLAG_LADDER, this.absmin, this.absmax, top_min, top_min, cost); -} - -spawnfunc(func_ladder) -{ - IL_PUSH(g_ladders, this); // TODO: also func_water? bots currently loop through func_ladder only - - func_ladder_init(this); -} - -spawnfunc(func_water) -{ - func_ladder_init(this); -} - -#elif defined(CSQC) -.float speed; - -void func_ladder_remove(entity this) -{ - strfree(this.classname); -} - -NET_HANDLE(ENT_CLIENT_LADDER, bool isnew) -{ - this.classname = strzone(ReadString()); - this.skin = ReadByte(); - this.speed = ReadCoord(); - - trigger_common_read(this, false); - - this.solid = SOLID_TRIGGER; - settouch(this, func_ladder_touch); - this.drawmask = MASK_NORMAL; - this.move_time = time; - this.entremove = func_ladder_remove; - - return true; -} -#endif diff --git a/qcsrc/common/triggers/func/ladder.qh b/qcsrc/common/triggers/func/ladder.qh deleted file mode 100644 index 26cbbda03..000000000 --- a/qcsrc/common/triggers/func/ladder.qh +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -.float ladder_time; -.entity ladder_entity; diff --git a/qcsrc/common/triggers/func/pendulum.qc b/qcsrc/common/triggers/func/pendulum.qc deleted file mode 100644 index a59f7a93b..000000000 --- a/qcsrc/common/triggers/func/pendulum.qc +++ /dev/null @@ -1,77 +0,0 @@ -#include "pendulum.qh" -#ifdef SVQC -.float freq; -void func_pendulum_controller_think(entity this) -{ - float v; - this.nextthink = time + 0.1; - - if (!(this.owner.active == ACTIVE_ACTIVE)) - { - this.owner.avelocity_x = 0; - return; - } - - // calculate sinewave using makevectors - makevectors((this.nextthink * this.owner.freq + this.owner.phase) * '0 360 0'); - v = this.owner.speed * v_forward_y + this.cnt; - if(this.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed - { - // * 10 so it will arrive in 0.1 sec - this.owner.avelocity_z = (remainder(v - this.owner.angles_z, 360)) * 10; - } -} - -spawnfunc(func_pendulum) -{ - entity controller; - if (this.noise != "") - { - precache_sound(this.noise); - soundto(MSG_INIT, this, CH_TRIGGER_SINGLE, this.noise, VOL_BASE, ATTEN_IDLE); - } - - this.active = ACTIVE_ACTIVE; - - // keys: angle, speed, phase, noise, freq - - if(!this.speed) - this.speed = 30; - // not initializing this.dmg to 2, to allow damageless pendulum - - if(this.dmg && (this.message == "")) - this.message = " was squished"; - if(this.dmg && (this.message2 == "")) - this.message2 = "was squished by"; - if(this.dmg && (!this.dmgtime)) - this.dmgtime = 0.25; - this.dmgtime2 = time; - - setblocked(this, generic_plat_blocked); - - this.avelocity_z = 0.0000001; - if (!InitMovingBrushTrigger(this)) - return; - - if(!this.freq) - { - // find pendulum length (same formula as Q3A) - this.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(this.mins_z)))); - } - - // copy initial angle - this.cnt = this.angles_z; - - // wait for targets to spawn - controller = new(func_pendulum_controller); - controller.owner = this; - controller.nextthink = time + 1; - setthink(controller, func_pendulum_controller_think); - this.nextthink = this.ltime + 999999999; - setthink(this, SUB_NullThink); // for PushMove - - //this.effects |= EF_LOWPRECISION; - - // TODO make a reset function for this one -} -#endif diff --git a/qcsrc/common/triggers/func/pendulum.qh b/qcsrc/common/triggers/func/pendulum.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/func/pendulum.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/func/plat.qc b/qcsrc/common/triggers/func/plat.qc deleted file mode 100644 index b05233621..000000000 --- a/qcsrc/common/triggers/func/plat.qc +++ /dev/null @@ -1,190 +0,0 @@ -#include "plat.qh" -REGISTER_NET_LINKED(ENT_CLIENT_PLAT) - -#ifdef SVQC -void plat_link(entity this); - -void plat_delayedinit(entity this) -{ - plat_link(this); - plat_spawn_inside_trigger(this); // the "start moving" trigger -} - -float plat_send(entity this, entity to, float sf) -{ - WriteHeader(MSG_ENTITY, ENT_CLIENT_PLAT); - WriteByte(MSG_ENTITY, sf); - - if(sf & SF_TRIGGER_INIT) - { - WriteByte(MSG_ENTITY, this.platmovetype_start); - WriteByte(MSG_ENTITY, this.platmovetype_turn); - WriteByte(MSG_ENTITY, this.platmovetype_end); - WriteByte(MSG_ENTITY, this.spawnflags); - - WriteString(MSG_ENTITY, this.model); - - trigger_common_write(this, true); - - WriteVector(MSG_ENTITY, this.pos1); - WriteVector(MSG_ENTITY, this.pos2); - - WriteVector(MSG_ENTITY, this.size); - - WriteAngle(MSG_ENTITY, this.mangle_x); - WriteAngle(MSG_ENTITY, this.mangle_y); - WriteAngle(MSG_ENTITY, this.mangle_z); - - WriteShort(MSG_ENTITY, this.speed); - WriteShort(MSG_ENTITY, this.height); - WriteByte(MSG_ENTITY, this.lip); - WriteByte(MSG_ENTITY, this.state); - - WriteShort(MSG_ENTITY, this.dmg); - } - - if(sf & SF_TRIGGER_RESET) - { - // used on client - } - - return true; -} - -void plat_link(entity this) -{ - //Net_LinkEntity(this, 0, false, plat_send); -} - -spawnfunc(func_plat) -{ - if (this.spawnflags & CRUSH) - { - this.dmg = 10000; - } - - if (this.dmg && (this.message == "")) - { - this.message = "was squished"; - } - if (this.dmg && (this.message2 == "")) - { - this.message2 = "was squished by"; - } - - if (this.sounds == 1) - { - this.noise = "plats/plat1.wav"; - this.noise1 = "plats/plat2.wav"; - } - - if (this.sounds == 2) - { - this.noise = "plats/medplat1.wav"; - this.noise1 = "plats/medplat2.wav"; - } - - // WARNING: backwards compatibility because people don't use already existing fields :( - if (this.sound1) - this.noise = this.sound1; - if (this.sound2) - this.noise1 = this.sound2; - - if(this.noise && this.noise != "") - { - precache_sound(this.noise); - } - if(this.noise1 && this.noise1 != "") - { - precache_sound(this.noise1); - } - - this.mangle = this.angles; - this.angles = '0 0 0'; - - this.classname = "plat"; - if (!InitMovingBrushTrigger(this)) - return; - this.effects |= EF_LOWPRECISION; - setsize (this, this.mins , this.maxs); - - setblocked(this, plat_crush); - - if (!this.speed) this.speed = 150; - if (!this.lip) this.lip = 16; - if (!this.height) this.height = this.size.z - this.lip; - - this.pos1 = this.origin; - this.pos2 = this.origin; - this.pos2_z = this.origin.z - this.height; - - this.reset = plat_reset; - this.reset(this); - - InitializeEntity(this, plat_delayedinit, INITPRIO_FINDTARGET); -} -#elif defined(CSQC) -void plat_draw(entity this) -{ - Movetype_Physics_NoMatchServer(this); - //Movetype_Physics_MatchServer(autocvar_cl_projectiles_sloppy); -} - -NET_HANDLE(ENT_CLIENT_PLAT, bool isnew) -{ - float sf = ReadByte(); - - if(sf & SF_TRIGGER_INIT) - { - this.platmovetype_start = ReadByte(); - this.platmovetype_turn = ReadByte(); - this.platmovetype_end = ReadByte(); - this.spawnflags = ReadByte(); - - this.model = strzone(ReadString()); - _setmodel(this, this.model); - - trigger_common_read(this, true); - - this.pos1 = ReadVector(); - this.pos2 = ReadVector(); - - this.size = ReadVector(); - - this.mangle_x = ReadAngle(); - this.mangle_y = ReadAngle(); - this.mangle_z = ReadAngle(); - - this.speed = ReadShort(); - this.height = ReadShort(); - this.lip = ReadByte(); - this.state = ReadByte(); - - this.dmg = ReadShort(); - - this.classname = "plat"; - this.solid = SOLID_BSP; - set_movetype(this, MOVETYPE_PUSH); - this.drawmask = MASK_NORMAL; - this.draw = plat_draw; - if (isnew) IL_PUSH(g_drawables, this); - this.use = plat_use; - this.entremove = trigger_remove_generic; - - plat_reset(this); // also called here - - set_movetype(this, MOVETYPE_PUSH); - this.move_time = time; - - plat_spawn_inside_trigger(this); - } - - if(sf & SF_TRIGGER_RESET) - { - plat_reset(this); - - this.move_time = time; - } - return true; -} -#endif diff --git a/qcsrc/common/triggers/func/plat.qh b/qcsrc/common/triggers/func/plat.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/func/plat.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/func/pointparticles.qc b/qcsrc/common/triggers/func/pointparticles.qc deleted file mode 100644 index 7de5a03ef..000000000 --- a/qcsrc/common/triggers/func/pointparticles.qc +++ /dev/null @@ -1,341 +0,0 @@ -#include "pointparticles.qh" -REGISTER_NET_LINKED(ENT_CLIENT_POINTPARTICLES) - -#ifdef SVQC -// NOTE: also contains func_sparks - -bool pointparticles_SendEntity(entity this, entity to, float sendflags) -{ - WriteHeader(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES); - - // optional features to save space - sendflags = sendflags & 0x0F; - if(this.spawnflags & PARTICLES_IMPULSE) - sendflags |= SF_POINTPARTICLES_IMPULSE; // absolute count on toggle-on - if(this.movedir != '0 0 0' || this.velocity != '0 0 0') - sendflags |= SF_POINTPARTICLES_MOVING; // 4 bytes - saves CPU - if(this.waterlevel || this.count != 1) - sendflags |= SF_POINTPARTICLES_JITTER_AND_COUNT; // 4 bytes - obscure features almost never used - if(this.mins != '0 0 0' || this.maxs != '0 0 0') - sendflags |= SF_POINTPARTICLES_BOUNDS; // 14 bytes - saves lots of space - - WriteByte(MSG_ENTITY, sendflags); - if(sendflags & SF_TRIGGER_UPDATE) - { - if(this.active == ACTIVE_ACTIVE) - WriteCoord(MSG_ENTITY, this.impulse); - else - WriteCoord(MSG_ENTITY, 0); // off - } - if(sendflags & SF_TRIGGER_RESET) - { - WriteVector(MSG_ENTITY, this.origin); - } - if(sendflags & SF_TRIGGER_INIT) - { - if(this.model != "null") - { - WriteShort(MSG_ENTITY, this.modelindex); - if(sendflags & SF_POINTPARTICLES_BOUNDS) - { - WriteVector(MSG_ENTITY, this.mins); - WriteVector(MSG_ENTITY, this.maxs); - } - } - else - { - WriteShort(MSG_ENTITY, 0); - if(sendflags & SF_POINTPARTICLES_BOUNDS) - { - WriteVector(MSG_ENTITY, this.maxs); - } - } - WriteShort(MSG_ENTITY, this.cnt); - WriteString(MSG_ENTITY, this.mdl); - if(sendflags & SF_POINTPARTICLES_MOVING) - { - WriteShort(MSG_ENTITY, compressShortVector(this.velocity)); - WriteShort(MSG_ENTITY, compressShortVector(this.movedir)); - } - if(sendflags & SF_POINTPARTICLES_JITTER_AND_COUNT) - { - WriteShort(MSG_ENTITY, this.waterlevel * 16.0); - WriteByte(MSG_ENTITY, this.count * 16.0); - } - WriteString(MSG_ENTITY, this.noise); - if(this.noise != "") - { - WriteByte(MSG_ENTITY, floor(this.atten * 64)); - WriteByte(MSG_ENTITY, floor(this.volume * 255)); - } - WriteString(MSG_ENTITY, this.bgmscript); - if(this.bgmscript != "") - { - WriteByte(MSG_ENTITY, floor(this.bgmscriptattack * 64)); - WriteByte(MSG_ENTITY, floor(this.bgmscriptdecay * 64)); - WriteByte(MSG_ENTITY, floor(this.bgmscriptsustain * 255)); - WriteByte(MSG_ENTITY, floor(this.bgmscriptrelease * 64)); - } - } - return 1; -} - -void pointparticles_think(entity this) -{ - if(this.origin != this.oldorigin) - { - this.SendFlags |= SF_TRIGGER_RESET; - this.oldorigin = this.origin; - } - this.nextthink = time; -} - -spawnfunc(func_pointparticles) -{ - if(this.model != "") { precache_model(this.model); _setmodel(this, this.model); } - if(this.noise != "") precache_sound(this.noise); - if(this.mdl != "") this.cnt = 0; // use a good handler - - if(!this.bgmscriptsustain) this.bgmscriptsustain = 1; - else if(this.bgmscriptsustain < 0) this.bgmscriptsustain = 0; - - if(!this.atten) this.atten = ATTEN_NORM; - else if(this.atten < 0) this.atten = 0; - if(!this.volume) this.volume = 1; - if(!this.count) this.count = 1; - if(!this.impulse) this.impulse = 1; - - if(!this.modelindex) - { - setorigin(this, this.origin + this.mins); - setsize(this, '0 0 0', this.maxs - this.mins); - } - //if(!this.cnt) this.cnt = _particleeffectnum(this.mdl); - this.setactive = generic_netlinked_setactive; - - Net_LinkEntity(this, (this.spawnflags & PARTICLES_VISCULLING), 0, pointparticles_SendEntity); - - IFTARGETED - { - // backwards compatibility - this.use = generic_netlinked_legacy_use; - } - this.reset = generic_netlinked_reset; - this.reset(this); - setthink(this, pointparticles_think); - this.nextthink = time; -} - -spawnfunc(func_sparks) -{ - if(this.count < 1) { - this.count = 25.0; // nice default value - } - - if(this.impulse < 0.5) { - this.impulse = 2.5; // nice default value - } - - this.mins = '0 0 0'; - this.maxs = '0 0 0'; - this.velocity = '0 0 -1'; - this.mdl = "TE_SPARK"; - this.cnt = 0; // use mdl - - spawnfunc_func_pointparticles(this); -} -#elif defined(CSQC) - -.int dphitcontentsmask; - -entityclass(PointParticles); -classfield(PointParticles) .int cnt; // effect number -classfield(PointParticles) .vector velocity; // particle velocity -classfield(PointParticles) .float waterlevel; // direction jitter -classfield(PointParticles) .int count; // count multiplier -classfield(PointParticles) .int impulse; // density -classfield(PointParticles) .string noise; // sound -classfield(PointParticles) .float atten; -classfield(PointParticles) .float volume; -classfield(PointParticles) .float absolute; // 1 = count per second is absolute, ABSOLUTE_ONLY_SPAWN_AT_TOGGLE = only spawn at toggle -classfield(PointParticles) .vector movedir; // trace direction -classfield(PointParticles) .float glow_color; // palette index - -const int ABSOLUTE_ONLY_SPAWN_AT_TOGGLE = 2; - -void Draw_PointParticles(entity this) -{ - float n, i, fail; - vector p; - vector sz; - vector o; - o = this.origin; - sz = this.maxs - this.mins; - n = doBGMScript(this); - if(this.absolute == ABSOLUTE_ONLY_SPAWN_AT_TOGGLE) - { - if(n >= 0) - n = this.just_toggled ? this.impulse : 0; - else - n = this.impulse * drawframetime; - } - else - { - n *= this.impulse * drawframetime; - if(this.just_toggled) - if(n < 1) - n = 1; - } - if(n == 0) - return; - fail = 0; - for(i = random(); i <= n && fail <= 64*n; ++i) - { - p = o + this.mins; - p.x += random() * sz.x; - p.y += random() * sz.y; - p.z += random() * sz.z; - if(WarpZoneLib_BoxTouchesBrush(p, p, this, NULL)) - { - if(this.movedir != '0 0 0') - { - traceline(p, p + normalize(this.movedir) * 4096, 0, NULL); - p = trace_endpos; - int eff_num; - if(this.cnt) - eff_num = this.cnt; - else - eff_num = _particleeffectnum(this.mdl); - __pointparticles(eff_num, p, trace_plane_normal * vlen(this.movedir) + this.velocity + randomvec() * this.waterlevel, this.count); - } - else - { - int eff_num; - if(this.cnt) - eff_num = this.cnt; - else - eff_num = _particleeffectnum(this.mdl); - __pointparticles(eff_num, p, this.velocity + randomvec() * this.waterlevel, this.count); - } - if(this.noise != "") - { - setorigin(this, p); - _sound(this, CH_AMBIENT, this.noise, VOL_BASE * this.volume, this.atten); - } - this.just_toggled = 0; - } - else if(this.absolute) - { - ++fail; - --i; - } - } - setorigin(this, o); -} - -void Ent_PointParticles_Remove(entity this) -{ - strfree(this.noise); - strfree(this.bgmscript); - strfree(this.mdl); -} - -NET_HANDLE(ENT_CLIENT_POINTPARTICLES, bool isnew) -{ - float i; - vector v; - int sendflags = ReadByte(); - if(sendflags & SF_TRIGGER_UPDATE) - { - i = ReadCoord(); // density (<0: point, >0: volume) - if(i && !this.impulse && (this.cnt || this.mdl)) // this.cnt check is so it only happens if the ent already existed - this.just_toggled = 1; - this.impulse = i; - } - if(sendflags & SF_TRIGGER_RESET) - { - this.origin = ReadVector(); - } - if(sendflags & SF_TRIGGER_INIT) - { - this.modelindex = ReadShort(); - if(sendflags & SF_POINTPARTICLES_BOUNDS) - { - if(this.modelindex) - { - this.mins = ReadVector(); - this.maxs = ReadVector(); - } - else - { - this.mins = '0 0 0'; - this.maxs = ReadVector(); - } - } - else - { - this.mins = this.maxs = '0 0 0'; - } - - this.cnt = ReadShort(); // effect number - this.mdl = strzone(ReadString()); // effect string - - if(sendflags & SF_POINTPARTICLES_MOVING) - { - this.velocity = decompressShortVector(ReadShort()); - this.movedir = decompressShortVector(ReadShort()); - } - else - { - this.velocity = this.movedir = '0 0 0'; - } - if(sendflags & SF_POINTPARTICLES_JITTER_AND_COUNT) - { - this.waterlevel = ReadShort() / 16.0; - this.count = ReadByte() / 16.0; - } - else - { - this.waterlevel = 0; - this.count = 1; - } - strcpy(this.noise, ReadString()); - if(this.noise != "") - { - this.atten = ReadByte() / 64.0; - this.volume = ReadByte() / 255.0; - } - strcpy(this.bgmscript, ReadString()); - if(this.bgmscript != "") - { - this.bgmscriptattack = ReadByte() / 64.0; - this.bgmscriptdecay = ReadByte() / 64.0; - this.bgmscriptsustain = ReadByte() / 255.0; - this.bgmscriptrelease = ReadByte() / 64.0; - } - BGMScript_InitEntity(this); - } - - return = true; - - if(sendflags & SF_TRIGGER_UPDATE) - { - this.absolute = (this.impulse >= 0); - if(!this.absolute) - { - v = this.maxs - this.mins; - this.impulse *= -v.x * v.y * v.z / (64**3); // relative: particles per 64^3 cube - } - } - - if(sendflags & SF_POINTPARTICLES_IMPULSE) - this.absolute = ABSOLUTE_ONLY_SPAWN_AT_TOGGLE; - - setorigin(this, this.origin); - setsize(this, this.mins, this.maxs); - this.solid = SOLID_NOT; - this.draw = Draw_PointParticles; - if (isnew) IL_PUSH(g_drawables, this); - this.entremove = Ent_PointParticles_Remove; -} -#endif diff --git a/qcsrc/common/triggers/func/pointparticles.qh b/qcsrc/common/triggers/func/pointparticles.qh deleted file mode 100644 index b167527bc..000000000 --- a/qcsrc/common/triggers/func/pointparticles.qh +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -// spawnflags -const int PARTICLES_IMPULSE = BIT(1); -const int PARTICLES_VISCULLING = BIT(2); - -// sendflags -const int SF_POINTPARTICLES_IMPULSE = BIT(4); -const int SF_POINTPARTICLES_MOVING = BIT(5); // Send velocity and movedir -const int SF_POINTPARTICLES_JITTER_AND_COUNT = BIT(6); // Send waterlevel (=jitter) and count -const int SF_POINTPARTICLES_BOUNDS = BIT(7); // Send min and max of the brush diff --git a/qcsrc/common/triggers/func/rainsnow.qc b/qcsrc/common/triggers/func/rainsnow.qc deleted file mode 100644 index c765a4293..000000000 --- a/qcsrc/common/triggers/func/rainsnow.qc +++ /dev/null @@ -1,142 +0,0 @@ -#include "rainsnow.qh" -REGISTER_NET_LINKED(ENT_CLIENT_RAINSNOW) - -#ifdef SVQC -bool rainsnow_SendEntity(entity this, entity to, float sf) -{ - vector myorg = this.origin + this.mins; - vector mysize = this.maxs - this.mins; - WriteHeader(MSG_ENTITY, ENT_CLIENT_RAINSNOW); - WriteByte(MSG_ENTITY, this.state); - WriteVector(MSG_ENTITY, myorg); - WriteVector(MSG_ENTITY, mysize); - WriteShort(MSG_ENTITY, compressShortVector(this.dest)); - WriteShort(MSG_ENTITY, this.count); - WriteByte(MSG_ENTITY, this.cnt); - return true; -} - -/*QUAKED spawnfunc_func_rain (0 .5 .8) ? -This is an invisible area like a trigger, which rain falls inside of. - -Keys: -"velocity" - falling direction (should be something like '0 0 -700', use the X and Y velocity for wind) -"cnt" - sets color of rain (default 12 - white) -"count" - adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000 -*/ -spawnfunc(func_rain) -{ - this.dest = this.velocity; - this.velocity = '0 0 0'; - if (!this.dest) - this.dest = '0 0 -700'; - this.angles = '0 0 0'; - set_movetype(this, MOVETYPE_NONE); - this.solid = SOLID_NOT; - SetBrushEntityModel(this); - if (!this.cnt) - { - this.cnt = 12; - } - if (!this.count) - this.count = 2000; - // relative to absolute particle count - this.count = 0.1 * this.count * (this.size_x / 1024) * (this.size_y / 1024); - if (this.count < 1) - this.count = 1; - if(this.count > 65535) - this.count = 65535; - - this.state = RAINSNOW_RAIN; - - Net_LinkEntity(this, false, 0, rainsnow_SendEntity); -} - - -/*QUAKED spawnfunc_func_snow (0 .5 .8) ? -This is an invisible area like a trigger, which snow falls inside of. - -Keys: -"velocity" - falling direction (should be something like '0 0 -300', use the X and Y velocity for wind) -"cnt" - sets color of rain (default 12 - white) -"count" - adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000 -*/ -spawnfunc(func_snow) -{ - this.dest = this.velocity; - this.velocity = '0 0 0'; - if (!this.dest) - this.dest = '0 0 -300'; - this.angles = '0 0 0'; - set_movetype(this, MOVETYPE_NONE); - this.solid = SOLID_NOT; - SetBrushEntityModel(this); - if (!this.cnt) - { - this.cnt = 12; - } - if (!this.count) - this.count = 2000; - // relative to absolute particle count - this.count = 0.1 * this.count * (this.size_x / 1024) * (this.size_y / 1024); - if (this.count < 1) - this.count = 1; - if(this.count > 65535) - this.count = 65535; - - this.state = RAINSNOW_SNOW; - - Net_LinkEntity(this, false, 0, rainsnow_SendEntity); -} -#elif defined(CSQC) -float autocvar_cl_rainsnow_maxdrawdist = 2048; - -void Draw_Rain(entity this) -{ - vector maxdist = '1 1 0' * autocvar_cl_rainsnow_maxdrawdist; - maxdist.z = 5; - if(boxesoverlap(vec2(view_origin) - maxdist, vec2(view_origin) + maxdist, vec2(this.absmin) - '0 0 5', vec2(this.absmax) + '0 0 5')) - //if(autocvar_cl_rainsnow_maxdrawdist <= 0 || vdist(vec2(this.origin) - vec2(this.absmin + this.absmax * 0.5), <=, autocvar_cl_rainsnow_maxdrawdist)) - te_particlerain(this.origin + this.mins, this.origin + this.maxs, this.velocity, floor(this.count * drawframetime + random()), this.glow_color); -} - -void Draw_Snow(entity this) -{ - vector maxdist = '1 1 0' * autocvar_cl_rainsnow_maxdrawdist; - maxdist.z = 5; - if(boxesoverlap(vec2(view_origin) - maxdist, vec2(view_origin) + maxdist, vec2(this.absmin) - '0 0 5', vec2(this.absmax) + '0 0 5')) - //if(autocvar_cl_rainsnow_maxdrawdist <= 0 || vdist(vec2(this.origin) - vec2(this.absmin + this.absmax * 0.5), <=, autocvar_cl_rainsnow_maxdrawdist)) - te_particlesnow(this.origin + this.mins, this.origin + this.maxs, this.velocity, floor(this.count * drawframetime + random()), this.glow_color); -} - -NET_HANDLE(ENT_CLIENT_RAINSNOW, bool isnew) -{ - this.state = ReadByte(); // Rain, Snow, or Whatever - this.origin = ReadVector(); - this.maxs = ReadVector(); - this.velocity = decompressShortVector(ReadShort()); - this.count = ReadShort(); - this.glow_color = ReadByte(); // color - - return = true; - - this.mins = -0.5 * this.maxs; - this.maxs = 0.5 * this.maxs; - this.origin = this.origin - this.mins; - - setorigin(this, this.origin); - setsize(this, this.mins, this.maxs); - this.solid = SOLID_NOT; - if (isnew) IL_PUSH(g_drawables, this); - if(this.state == RAINSNOW_RAIN) - this.draw = Draw_Rain; - else - this.draw = Draw_Snow; -} -#endif diff --git a/qcsrc/common/triggers/func/rainsnow.qh b/qcsrc/common/triggers/func/rainsnow.qh deleted file mode 100644 index d60eb4f48..000000000 --- a/qcsrc/common/triggers/func/rainsnow.qh +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - - -const int RAINSNOW_SNOW = 0; -const int RAINSNOW_RAIN = 1; diff --git a/qcsrc/common/triggers/func/rotating.qc b/qcsrc/common/triggers/func/rotating.qc deleted file mode 100644 index 35351ee08..000000000 --- a/qcsrc/common/triggers/func/rotating.qc +++ /dev/null @@ -1,110 +0,0 @@ -#include "rotating.qh" -#ifdef SVQC - -void func_rotating_setactive(entity this, int astate) -{ - if (astate == ACTIVE_TOGGLE) - { - if(this.active == ACTIVE_ACTIVE) - this.active = ACTIVE_NOT; - else - this.active = ACTIVE_ACTIVE; - } - else - this.active = astate; - - if(this.active == ACTIVE_NOT) - { - this.avelocity = '0 0 0'; - stopsound(this, CH_AMBIENT_SINGLE); - } - else - { - this.avelocity = this.pos1; - if(this.noise && this.noise != "") - { - _sound(this, CH_AMBIENT_SINGLE, this.noise, VOL_BASE, ATTEN_IDLE); - } - } -} - -void func_rotating_reset(entity this) -{ - // TODO: reset angles as well? - - if(this.spawnflags & FUNC_ROTATING_STARTOFF) - { - this.setactive(this, ACTIVE_NOT); - } - else - { - this.setactive(this, ACTIVE_ACTIVE); - } -} - -void func_rotating_init_for_player(entity this, entity player) -{ - if (this.noise && this.noise != "" && this.active == ACTIVE_ACTIVE && IS_REAL_CLIENT(player)) - { - msg_entity = player; - soundto (MSG_ONE, this, CH_AMBIENT_SINGLE, this.noise, VOL_BASE, ATTEN_IDLE); - } -} - -/*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS -Brush model that spins in place on one axis (default Z). -speed : speed to rotate (in degrees per second) -noise : path/name of looping .wav file to play. -dmg : Do this mutch dmg every .dmgtime intervall when blocked -dmgtime : See above. -*/ - -spawnfunc(func_rotating) -{ - if (this.noise && this.noise != "") - { - precache_sound(this.noise); - } - - this.setactive = func_rotating_setactive; - - if (!this.speed) - this.speed = 100; - if (this.spawnflags & FUNC_ROTATING_XAXIS) - this.avelocity = '0 0 1' * this.speed; - else if (this.spawnflags & FUNC_ROTATING_YAXIS) - this.avelocity = '1 0 0' * this.speed; - else // Z - this.avelocity = '0 1 0' * this.speed; - - this.pos1 = this.avelocity; - - if(this.dmg && (this.message == "")) - this.message = " was squished"; - if(this.dmg && (this.message2 == "")) - this.message2 = "was squished by"; - - - if(this.dmg && (!this.dmgtime)) - this.dmgtime = 0.25; - - this.dmgtime2 = time; - - if (!InitMovingBrushTrigger(this)) - return; - // no EF_LOWPRECISION here, as rounding angles is bad - - setblocked(this, generic_plat_blocked); - - // wait for targets to spawn - this.nextthink = this.ltime + 999999999; - setthink(this, SUB_NullThink); // for PushMove - - this.reset = func_rotating_reset; - this.reset(this); - - // maybe send sound to new players - IL_PUSH(g_initforplayer, this); - this.init_for_player = func_rotating_init_for_player; -} -#endif diff --git a/qcsrc/common/triggers/func/rotating.qh b/qcsrc/common/triggers/func/rotating.qh deleted file mode 100644 index ad1b6ec92..000000000 --- a/qcsrc/common/triggers/func/rotating.qh +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - - -const int FUNC_ROTATING_XAXIS = BIT(2); -const int FUNC_ROTATING_YAXIS = BIT(3); -const int FUNC_ROTATING_STARTOFF = BIT(4); diff --git a/qcsrc/common/triggers/func/stardust.qc b/qcsrc/common/triggers/func/stardust.qc deleted file mode 100644 index 9c2fba8ad..000000000 --- a/qcsrc/common/triggers/func/stardust.qc +++ /dev/null @@ -1,9 +0,0 @@ -#include "stardust.qh" -#ifdef SVQC -spawnfunc(func_stardust) -{ - this.effects = EF_STARDUST; - - CSQCMODEL_AUTOINIT(this); -} -#endif diff --git a/qcsrc/common/triggers/func/stardust.qh b/qcsrc/common/triggers/func/stardust.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/func/stardust.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/func/train.qc b/qcsrc/common/triggers/func/train.qc deleted file mode 100644 index 4e9c33456..000000000 --- a/qcsrc/common/triggers/func/train.qc +++ /dev/null @@ -1,351 +0,0 @@ -#include "train.qh" -.float train_wait_turning; -.entity future_target; -void train_next(entity this); -#ifdef SVQC -void train_use(entity this, entity actor, entity trigger); -#endif -void train_wait(entity this) -{ - SUB_UseTargets(this.enemy, NULL, NULL); - this.enemy = NULL; - - // if turning is enabled, the train will turn toward the next point while waiting - if(this.platmovetype_turn && !this.train_wait_turning) - { - entity targ, cp; - vector ang; - targ = this.future_target; - if((this.spawnflags & TRAIN_CURVE) && targ.curvetarget) - cp = find(NULL, targetname, targ.curvetarget); - else - cp = NULL; - - if(cp) // bezier curves movement - ang = cp.origin - (this.origin - this.view_ofs); // use the origin of the control point of the next path_corner - else // linear movement - ang = targ.origin - (this.origin - this.view_ofs); // use the origin of the next path_corner - ang = vectoangles(ang); - ang_x = -ang_x; // flip up / down orientation - - if(this.wait > 0) // slow turning - SUB_CalcAngleMove(this, ang, TSPEED_TIME, this.ltime - time + this.wait, train_wait); - else // instant turning - SUB_CalcAngleMove(this, ang, TSPEED_TIME, 0.0000001, train_wait); - this.train_wait_turning = true; - return; - } - -#ifdef SVQC - if(this.noise != "") - stopsoundto(MSG_BROADCAST, this, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway -#endif - -#ifdef SVQC - entity tg = this.future_target; - if(tg.spawnflags & TRAIN_NEEDACTIVATION) - { - this.use = train_use; - setthink(this, func_null); - this.nextthink = 0; - } - else -#endif - if(this.wait < 0 || this.train_wait_turning) // no waiting or we already waited while turning - { - this.train_wait_turning = false; - train_next(this); - } - else - { - setthink(this, train_next); - this.nextthink = this.ltime + this.wait; - } -} - -entity train_next_find(entity this) -{ - if(this.target_random) - { - RandomSelection_Init(); - for(entity t = NULL; (t = find(t, targetname, this.target));) - { - RandomSelection_AddEnt(t, 1, 0); - } - return RandomSelection_chosen_ent; - } - else - { - return find(NULL, targetname, this.target); - } -} - -void train_next(entity this) -{ - entity targ = NULL, cp = NULL; - vector cp_org = '0 0 0'; - - targ = this.future_target; - - this.target = targ.target; - this.target_random = targ.target_random; - this.future_target = train_next_find(targ); - - if (this.spawnflags & TRAIN_CURVE) - { - if(targ.curvetarget) - { - cp = find(NULL, targetname, targ.curvetarget); // get its second target (the control point) - cp_org = cp.origin - this.view_ofs; // no control point found, assume a straight line to the destination - } - } - if (this.target == "") - objerror(this, "train_next: no next target"); - this.wait = targ.wait; - if (!this.wait) - this.wait = 0.1; - - if(targ.platmovetype) - { - // this path_corner contains a movetype overrider, apply it - this.platmovetype_start = targ.platmovetype_start; - this.platmovetype_end = targ.platmovetype_end; - } - else - { - // this path_corner doesn't contain a movetype overrider, use the train's defaults - this.platmovetype_start = this.platmovetype_start_default; - this.platmovetype_end = this.platmovetype_end_default; - } - - if (targ.speed) - { - if (cp) - SUB_CalcMove_Bezier(this, cp_org, targ.origin - this.view_ofs, TSPEED_LINEAR, targ.speed, train_wait); - else - SUB_CalcMove(this, targ.origin - this.view_ofs, TSPEED_LINEAR, targ.speed, train_wait); - } - else - { - if (cp) - SUB_CalcMove_Bezier(this, cp_org, targ.origin - this.view_ofs, TSPEED_LINEAR, this.speed, train_wait); - else - SUB_CalcMove(this, targ.origin - this.view_ofs, TSPEED_LINEAR, this.speed, train_wait); - } - - if(this.noise != "") - _sound(this, CH_TRIGGER_SINGLE, this.noise, VOL_BASE, ATTEN_IDLE); -} - -REGISTER_NET_LINKED(ENT_CLIENT_TRAIN) - -#ifdef SVQC -float train_send(entity this, entity to, float sf) -{ - WriteHeader(MSG_ENTITY, ENT_CLIENT_TRAIN); - WriteByte(MSG_ENTITY, sf); - - if(sf & SF_TRIGGER_INIT) - { - WriteString(MSG_ENTITY, this.platmovetype); - WriteByte(MSG_ENTITY, this.platmovetype_turn); - WriteByte(MSG_ENTITY, this.spawnflags); - - WriteString(MSG_ENTITY, this.model); - - trigger_common_write(this, true); - - WriteString(MSG_ENTITY, this.curvetarget); - - WriteVector(MSG_ENTITY, this.pos1); - WriteVector(MSG_ENTITY, this.pos2); - - WriteVector(MSG_ENTITY, this.size); - - WriteVector(MSG_ENTITY, this.view_ofs); - - WriteAngle(MSG_ENTITY, this.mangle_x); - WriteAngle(MSG_ENTITY, this.mangle_y); - WriteAngle(MSG_ENTITY, this.mangle_z); - - WriteShort(MSG_ENTITY, this.speed); - WriteShort(MSG_ENTITY, this.height); - WriteByte(MSG_ENTITY, this.lip); - WriteByte(MSG_ENTITY, this.state); - WriteByte(MSG_ENTITY, this.wait); - - WriteShort(MSG_ENTITY, this.dmg); - WriteByte(MSG_ENTITY, this.dmgtime); - } - - if(sf & SF_TRIGGER_RESET) - { - // used on client - } - - return true; -} - -void train_link(entity this) -{ - //Net_LinkEntity(this, 0, false, train_send); -} - -void train_use(entity this, entity actor, entity trigger) -{ - this.nextthink = this.ltime + 1; - setthink(this, train_next); - this.use = func_null; // not again, next target can set it again if needed - if(trigger.target2 && trigger.target2 != "") - this.future_target = find(NULL, targetname, trigger.target2); -} - -void func_train_find(entity this) -{ - entity targ = train_next_find(this); - this.target = targ.target; - this.target_random = targ.target_random; - // save the future target for later - this.future_target = train_next_find(targ); - if (this.target == "") - objerror(this, "func_train_find: no next target"); - setorigin(this, targ.origin - this.view_ofs); - - if(!(this.spawnflags & TRAIN_NEEDACTIVATION)) - { - this.nextthink = this.ltime + 1; - setthink(this, train_next); - } - - train_link(this); -} - -#endif - -/*QUAKED spawnfunc_func_train (0 .5 .8) ? -Ridable platform, targets spawnfunc_path_corner path to follow. -speed : speed the train moves (can be overridden by each spawnfunc_path_corner) -target : targetname of first spawnfunc_path_corner (starts here) -*/ -#ifdef SVQC -spawnfunc(func_train) -{ - if (this.noise != "") - precache_sound(this.noise); - - if (this.target == "") - objerror(this, "func_train without a target"); - if (!this.speed) - this.speed = 100; - - if (!InitMovingBrushTrigger(this)) - return; - this.effects |= EF_LOWPRECISION; - - if(this.spawnflags & TRAIN_NEEDACTIVATION) - this.use = train_use; - - if (this.spawnflags & TRAIN_TURN) - { - this.platmovetype_turn = true; - this.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now - } - else - this.view_ofs = this.mins; - - // wait for targets to spawn - InitializeEntity(this, func_train_find, INITPRIO_FINDTARGET); - - setblocked(this, generic_plat_blocked); - if(this.dmg && (this.message == "")) - this.message = " was squished"; - if(this.dmg && (this.message2 == "")) - this.message2 = "was squished by"; - if(this.dmg && (!this.dmgtime)) - this.dmgtime = 0.25; - this.dmgtime2 = time; - - if(!set_platmovetype(this, this.platmovetype)) - return; - this.platmovetype_start_default = this.platmovetype_start; - this.platmovetype_end_default = this.platmovetype_end; - - // TODO make a reset function for this one -} -#elif defined(CSQC) -void train_draw(entity this) -{ - //Movetype_Physics_NoMatchServer(); - Movetype_Physics_MatchServer(this, autocvar_cl_projectiles_sloppy); -} - -NET_HANDLE(ENT_CLIENT_TRAIN, bool isnew) -{ - float sf = ReadByte(); - - if(sf & SF_TRIGGER_INIT) - { - this.platmovetype = strzone(ReadString()); - this.platmovetype_turn = ReadByte(); - this.spawnflags = ReadByte(); - - this.model = strzone(ReadString()); - _setmodel(this, this.model); - - trigger_common_read(this, true); - - this.curvetarget = strzone(ReadString()); - - this.pos1 = ReadVector(); - this.pos2 = ReadVector(); - - this.size = ReadVector(); - - this.view_ofs = ReadVector(); - - this.mangle_x = ReadAngle(); - this.mangle_y = ReadAngle(); - this.mangle_z = ReadAngle(); - - this.speed = ReadShort(); - this.height = ReadShort(); - this.lip = ReadByte(); - this.state = ReadByte(); - this.wait = ReadByte(); - - this.dmg = ReadShort(); - this.dmgtime = ReadByte(); - - this.classname = "func_train"; - this.solid = SOLID_BSP; - set_movetype(this, MOVETYPE_PUSH); - this.drawmask = MASK_NORMAL; - this.draw = train_draw; - if (isnew) IL_PUSH(g_drawables, this); - this.entremove = trigger_remove_generic; - - if(set_platmovetype(this, this.platmovetype)) - { - this.platmovetype_start_default = this.platmovetype_start; - this.platmovetype_end_default = this.platmovetype_end; - } - - // everything is set up by the time the train is linked, we shouldn't need this - //func_train_find(); - - // but we will need these - train_next(this); - - set_movetype(this, MOVETYPE_PUSH); - this.move_time = time; - } - - if(sf & SF_TRIGGER_RESET) - { - // TODO: make a reset function for trains - } - - return true; -} - -#endif diff --git a/qcsrc/common/triggers/func/train.qh b/qcsrc/common/triggers/func/train.qh deleted file mode 100644 index 0b2a099c5..000000000 --- a/qcsrc/common/triggers/func/train.qh +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - - -const int TRAIN_CURVE = BIT(0); -const int TRAIN_TURN = BIT(1); -const int TRAIN_NEEDACTIVATION = BIT(2); - -#ifdef CSQC -.float dmgtime; -#endif diff --git a/qcsrc/common/triggers/func/vectormamamam.qc b/qcsrc/common/triggers/func/vectormamamam.qc deleted file mode 100644 index 61da52acb..000000000 --- a/qcsrc/common/triggers/func/vectormamamam.qc +++ /dev/null @@ -1,194 +0,0 @@ -#include "vectormamamam.qh" -#ifdef SVQC -// reusing some fields havocbots declared -.entity wp00, wp01, wp02, wp03; - -.float targetfactor, target2factor, target3factor, target4factor; -.vector targetnormal, target2normal, target3normal, target4normal; - -vector func_vectormamamam_origin(entity o, float timestep) -{ - vector v, p; - float flags; - entity e; - - flags = o.spawnflags; - v = '0 0 0'; - - e = o.wp00; - if(e) - { - p = e.origin + timestep * e.velocity; - if(flags & PROJECT_ON_TARGETNORMAL) - v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor; - else - v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor; - } - - e = o.wp01; - if(e) - { - p = e.origin + timestep * e.velocity; - if(flags & PROJECT_ON_TARGET2NORMAL) - v = v + (p * o.target2normal) * o.target2normal * o.target2factor; - else - v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor; - } - - e = o.wp02; - if(e) - { - p = e.origin + timestep * e.velocity; - if(flags & PROJECT_ON_TARGET3NORMAL) - v = v + (p * o.target3normal) * o.target3normal * o.target3factor; - else - v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor; - } - - e = o.wp03; - if(e) - { - p = e.origin + timestep * e.velocity; - if(flags & PROJECT_ON_TARGET4NORMAL) - v = v + (p * o.target4normal) * o.target4normal * o.target4factor; - else - v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor; - } - - return v; -} - -void func_vectormamamam_controller_think(entity this) -{ - this.nextthink = time + vectormamamam_timestep; - - if(this.owner.active != ACTIVE_ACTIVE) - { - this.owner.velocity = '0 0 0'; - return; - } - - if(this.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed - this.owner.velocity = (this.owner.destvec + func_vectormamamam_origin(this.owner, vectormamamam_timestep) - this.owner.origin) * 10; -} - -void func_vectormamamam_findtarget(entity this) -{ - if(this.target != "") - this.wp00 = find(NULL, targetname, this.target); - - if(this.target2 != "") - this.wp01 = find(NULL, targetname, this.target2); - - if(this.target3 != "") - this.wp02 = find(NULL, targetname, this.target3); - - if(this.target4 != "") - this.wp03 = find(NULL, targetname, this.target4); - - if(!this.wp00 && !this.wp01 && !this.wp02 && !this.wp03) - objerror(this, "No reference entity found, so there is nothing to move. Aborting."); - - this.destvec = this.origin - func_vectormamamam_origin(this, 0); - - entity controller; - controller = new(func_vectormamamam_controller); - controller.owner = this; - controller.nextthink = time + 1; - setthink(controller, func_vectormamamam_controller_think); -} - -void func_vectormamamam_setactive(entity this, int astate) -{ - if (astate == ACTIVE_TOGGLE) - { - if(this.active == ACTIVE_ACTIVE) - this.active = ACTIVE_NOT; - else - this.active = ACTIVE_ACTIVE; - } - else - this.active = astate; - - if(this.active == ACTIVE_NOT) - { - stopsound(this, CH_TRIGGER_SINGLE); - } - else - { - if(this.noise && this.noise != "") - { - _sound(this, CH_TRIGGER_SINGLE, this.noise, VOL_BASE, ATTEN_IDLE); - } - } -} - -void func_vectormamamam_init_for_player(entity this, entity player) -{ - if (this.noise && this.noise != "" && this.active == ACTIVE_ACTIVE && IS_REAL_CLIENT(player)) - { - msg_entity = player; - soundto(MSG_ONE, this, CH_TRIGGER_SINGLE, this.noise, VOL_BASE, ATTEN_IDLE); - } -} - -spawnfunc(func_vectormamamam) -{ - if (this.noise != "") - { - precache_sound(this.noise); - } - - if(!this.targetfactor) - this.targetfactor = 1; - - if(!this.target2factor) - this.target2factor = 1; - - if(!this.target3factor) - this.target3factor = 1; - - if(!this.target4factor) - this.target4factor = 1; - - if(this.targetnormal) - this.targetnormal = normalize(this.targetnormal); - - if(this.target2normal) - this.target2normal = normalize(this.target2normal); - - if(this.target3normal) - this.target3normal = normalize(this.target3normal); - - if(this.target4normal) - this.target4normal = normalize(this.target4normal); - - setblocked(this, generic_plat_blocked); - if(this.dmg && (this.message == "")) - this.message = " was squished"; - if(this.dmg && (this.message == "")) - this.message2 = "was squished by"; - if(this.dmg && (!this.dmgtime)) - this.dmgtime = 0.25; - this.dmgtime2 = time; - - if (!InitMovingBrushTrigger(this)) - return; - - // wait for targets to spawn - this.nextthink = this.ltime + 999999999; - setthink(this, SUB_NullThink); // for PushMove - - // Savage: Reduce bandwith, critical on e.g. nexdm02 - this.effects |= EF_LOWPRECISION; - - this.setactive = func_vectormamamam_setactive; - this.setactive(this, ACTIVE_ACTIVE); - - // maybe send sound to new players - IL_PUSH(g_initforplayer, this); - this.init_for_player = func_vectormamamam_init_for_player; - - InitializeEntity(this, func_vectormamamam_findtarget, INITPRIO_FINDTARGET); -} -#endif diff --git a/qcsrc/common/triggers/func/vectormamamam.qh b/qcsrc/common/triggers/func/vectormamamam.qh deleted file mode 100644 index 7eb6b0a63..000000000 --- a/qcsrc/common/triggers/func/vectormamamam.qh +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - - -const int PROJECT_ON_TARGETNORMAL = BIT(0); -const int PROJECT_ON_TARGET2NORMAL = BIT(1); -const int PROJECT_ON_TARGET3NORMAL = BIT(2); -const int PROJECT_ON_TARGET4NORMAL = BIT(3); - -const float vectormamamam_timestep = 0.1; diff --git a/qcsrc/common/triggers/include.qc b/qcsrc/common/triggers/include.qc deleted file mode 100644 index 59da1f217..000000000 --- a/qcsrc/common/triggers/include.qc +++ /dev/null @@ -1,20 +0,0 @@ -#include "include.qh" - -// some required common stuff -#include "subs.qc" -#include "triggers.qc" -#include "platforms.qc" -#include "teleporters.qc" - -// func -#include "func/include.qc" - -// misc -#include "misc/include.qc" - -// target -#include "target/include.qc" - -// trigger -#include "trigger/include.qc" - diff --git a/qcsrc/common/triggers/include.qh b/qcsrc/common/triggers/include.qh deleted file mode 100644 index 87c07c14d..000000000 --- a/qcsrc/common/triggers/include.qh +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -// some required common stuff -#ifdef SVQC - #include -#endif -#include "triggers.qh" -#include "subs.qh" -#include "platforms.qh" -#include "teleporters.qh" - -// func -#include "func/include.qh" - -// target -#include "target/include.qh" - -// trigger -#include "trigger/include.qh" diff --git a/qcsrc/common/triggers/misc/_mod.inc b/qcsrc/common/triggers/misc/_mod.inc deleted file mode 100644 index 4a8ec06ef..000000000 --- a/qcsrc/common/triggers/misc/_mod.inc +++ /dev/null @@ -1,6 +0,0 @@ -// generated file; do not modify -#include -#include -#include -#include -#include diff --git a/qcsrc/common/triggers/misc/_mod.qh b/qcsrc/common/triggers/misc/_mod.qh deleted file mode 100644 index 98615ccb0..000000000 --- a/qcsrc/common/triggers/misc/_mod.qh +++ /dev/null @@ -1,6 +0,0 @@ -// generated file; do not modify -#include -#include -#include -#include -#include diff --git a/qcsrc/common/triggers/misc/corner.qc b/qcsrc/common/triggers/misc/corner.qc deleted file mode 100644 index a0f67b759..000000000 --- a/qcsrc/common/triggers/misc/corner.qc +++ /dev/null @@ -1,75 +0,0 @@ -#include "corner.qh" -REGISTER_NET_LINKED(ENT_CLIENT_CORNER) - -#ifdef SVQC -bool corner_send(entity this, entity to, int sf) -{ - WriteHeader(MSG_ENTITY, ENT_CLIENT_CORNER); - - WriteString(MSG_ENTITY, this.platmovetype); - - WriteVector(MSG_ENTITY, this.origin); - - WriteString(MSG_ENTITY, this.target); - WriteString(MSG_ENTITY, this.target2); - WriteString(MSG_ENTITY, this.target3); - WriteString(MSG_ENTITY, this.target4); - WriteString(MSG_ENTITY, this.targetname); - WriteByte(MSG_ENTITY, this.target_random); - - WriteByte(MSG_ENTITY, this.wait); - - return true; -} - -void corner_link(entity this) -{ - //Net_LinkEntity(this, false, 0, corner_send); -} - -spawnfunc(path_corner) -{ - // setup values for overriding train movement - // if a second value does not exist, both start and end speeds are the single value specified - set_platmovetype(this, this.platmovetype); - - corner_link(this); -} -#elif defined(CSQC) - -void corner_remove(entity this) -{ - strfree(this.target); - strfree(this.target2); - strfree(this.target3); - strfree(this.target4); - strfree(this.targetname); - strfree(this.platmovetype); -} - -NET_HANDLE(ENT_CLIENT_CORNER, bool isnew) -{ - this.platmovetype = strzone(ReadString()); - - this.origin = ReadVector(); - setorigin(this, this.origin); - - this.target = strzone(ReadString()); - this.target2 = strzone(ReadString()); - this.target3 = strzone(ReadString()); - this.target4 = strzone(ReadString()); - this.targetname = strzone(ReadString()); - this.target_random = ReadByte(); - - this.wait = ReadByte(); - - return = true; - - this.classname = "path_corner"; - this.drawmask = MASK_NORMAL; - this.entremove = corner_remove; - - set_platmovetype(this, this.platmovetype); -} - -#endif diff --git a/qcsrc/common/triggers/misc/corner.qh b/qcsrc/common/triggers/misc/corner.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/misc/corner.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/misc/follow.qc b/qcsrc/common/triggers/misc/follow.qc deleted file mode 100644 index 87619ca71..000000000 --- a/qcsrc/common/triggers/misc/follow.qc +++ /dev/null @@ -1,70 +0,0 @@ -#include "follow.qh" -// the way this entity works makes it no use to CSQC, as it removes itself instantly - -#ifdef SVQC -void follow_init(entity this) -{ - entity src, dst; - src = NULL; - dst = NULL; - if(this.killtarget != "") - src = find(NULL, targetname, this.killtarget); - if(this.target != "") - dst = find(NULL, targetname, this.target); - - if(!src && !dst) - { - objerror(this, "follow: could not find target/killtarget"); - return; - } - - if(this.jointtype) - { - // already done :P entity must stay - this.aiment = src; - this.enemy = dst; - } - else if(!src || !dst) - { - objerror(this, "follow: could not find target/killtarget"); - return; - } - else if(this.spawnflags & FOLLOW_ATTACH) - { - // attach - if(this.spawnflags & FOLLOW_LOCAL) - { - setattachment(dst, src, this.message); - } - else - { - attach_sameorigin(dst, src, this.message); - } - - dst.solid = SOLID_NOT; // solid doesn't work with attachment - delete(this); - } - else - { - if(this.spawnflags & FOLLOW_LOCAL) - { - set_movetype(dst, MOVETYPE_FOLLOW); - dst.aiment = src; - // dst.punchangle = '0 0 0'; // keep unchanged - dst.view_ofs = dst.origin; - dst.v_angle = dst.angles; - } - else - { - follow_sameorigin(dst, src); - } - - delete(this); - } -} - -spawnfunc(misc_follow) -{ - InitializeEntity(this, follow_init, INITPRIO_FINDTARGET); -} -#endif diff --git a/qcsrc/common/triggers/misc/follow.qh b/qcsrc/common/triggers/misc/follow.qh deleted file mode 100644 index aef491ff3..000000000 --- a/qcsrc/common/triggers/misc/follow.qh +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - - -const int FOLLOW_ATTACH = BIT(0); -const int FOLLOW_LOCAL = BIT(1); diff --git a/qcsrc/common/triggers/misc/include.qc b/qcsrc/common/triggers/misc/include.qc deleted file mode 100644 index bbe5ce03d..000000000 --- a/qcsrc/common/triggers/misc/include.qc +++ /dev/null @@ -1,5 +0,0 @@ -#include "include.qh" -#include "corner.qc" -#include "follow.qc" -#include "laser.qc" -#include "teleport_dest.qc" diff --git a/qcsrc/common/triggers/misc/include.qh b/qcsrc/common/triggers/misc/include.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/misc/include.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/misc/laser.qc b/qcsrc/common/triggers/misc/laser.qc deleted file mode 100644 index df88b750f..000000000 --- a/qcsrc/common/triggers/misc/laser.qc +++ /dev/null @@ -1,418 +0,0 @@ -#include "laser.qh" -#if defined(CSQC) - #include - #include - #include -#elif defined(MENUQC) -#elif defined(SVQC) -#endif - -REGISTER_NET_LINKED(ENT_CLIENT_LASER) - -#ifdef SVQC -.float modelscale; -void misc_laser_aim(entity this) -{ - vector a; - if(this.enemy) - { - if(this.spawnflags & LASER_FINITE) - { - if(this.enemy.origin != this.mangle) - { - this.mangle = this.enemy.origin; - this.SendFlags |= SF_LASER_UPDATE_TARGET; - } - } - else - { - a = vectoangles(this.enemy.origin - this.origin); - a_x = -a_x; - if(a != this.mangle) - { - this.mangle = a; - this.SendFlags |= SF_LASER_UPDATE_TARGET; - } - } - } - else - { - if(this.angles != this.mangle) - { - this.mangle = this.angles; - this.SendFlags |= SF_LASER_UPDATE_TARGET; - } - } - if(this.origin != this.oldorigin) - { - this.SendFlags |= SF_LASER_UPDATE_ORIGIN; - this.oldorigin = this.origin; - } -} - -void misc_laser_init(entity this) -{ - if(this.target != "") - this.enemy = find(NULL, targetname, this.target); -} - -.entity pusher; -void misc_laser_think(entity this) -{ - vector o; - entity hitent; - vector hitloc; - - this.nextthink = time; - - if(this.active == ACTIVE_NOT) - return; - - misc_laser_aim(this); - - if(this.enemy) - { - o = this.enemy.origin; - if (!(this.spawnflags & LASER_FINITE)) - o = this.origin + normalize(o - this.origin) * LASER_BEAM_MAXLENGTH; - } - else - { - makevectors(this.mangle); - o = this.origin + v_forward * LASER_BEAM_MAXLENGTH; - } - - if(this.dmg || this.enemy.target != "") - { - traceline(this.origin, o, MOVE_NORMAL, this); - } - hitent = trace_ent; - hitloc = trace_endpos; - - if(this.enemy.target != "") // DETECTOR laser - { - if(trace_ent.iscreature) - { - this.pusher = hitent; - if(!this.count) - { - this.count = 1; - - SUB_UseTargets(this.enemy, this.enemy.pusher, NULL); - } - } - else - { - if(this.count) - { - this.count = 0; - - SUB_UseTargets(this.enemy, this.enemy.pusher, NULL); - } - } - } - - if(this.dmg) - { - if(this.team) - if(((this.spawnflags & LASER_INVERT_TEAM) == 0) == (this.team != hitent.team)) - return; - if(hitent.takedamage) - Damage(hitent, this, this, ((this.dmg < 0) ? 100000 : (this.dmg * frametime)), DEATH_HURTTRIGGER.m_id, DMG_NOWEP, hitloc, '0 0 0'); - } -} - -bool laser_SendEntity(entity this, entity to, float sendflags) -{ - WriteHeader(MSG_ENTITY, ENT_CLIENT_LASER); - sendflags = sendflags & 0x0F; // use that bit to indicate finite length laser - if(this.spawnflags & LASER_FINITE) - sendflags |= SF_LASER_FINITE; - if(this.alpha) - sendflags |= SF_LASER_ALPHA; - if(this.scale != 1 || this.modelscale != 1) - sendflags |= SF_LASER_SCALE; - if(this.spawnflags & LASER_NOTRACE) - sendflags |= SF_LASER_NOTRACE; - WriteByte(MSG_ENTITY, sendflags); - if(sendflags & SF_LASER_UPDATE_ORIGIN) - { - WriteVector(MSG_ENTITY, this.origin); - } - if(sendflags & SF_LASER_UPDATE_EFFECT) - { - WriteByte(MSG_ENTITY, this.beam_color.x * 255.0); - WriteByte(MSG_ENTITY, this.beam_color.y * 255.0); - WriteByte(MSG_ENTITY, this.beam_color.z * 255.0); - if(sendflags & SF_LASER_ALPHA) - WriteByte(MSG_ENTITY, this.alpha * 255.0); - if(sendflags & SF_LASER_SCALE) - { - WriteByte(MSG_ENTITY, bound(0, this.scale * 16.0, 255)); - WriteByte(MSG_ENTITY, bound(0, this.modelscale * 16.0, 255)); - } - if((sendflags & SF_LASER_FINITE) || !(sendflags & SF_LASER_NOTRACE)) // effect doesn't need sending if the laser is infinite and has collision testing turned off - WriteShort(MSG_ENTITY, this.cnt); - } - if(sendflags & SF_LASER_UPDATE_TARGET) - { - if(sendflags & SF_LASER_FINITE) - { - WriteVector(MSG_ENTITY, this.enemy.origin); - } - else - { - WriteAngle(MSG_ENTITY, this.mangle_x); - WriteAngle(MSG_ENTITY, this.mangle_y); - } - } - if(sendflags & SF_LASER_UPDATE_ACTIVE) - WriteByte(MSG_ENTITY, this.active); - return true; -} - -/*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED -Any object touching the beam will be hurt -Keys: -"target" - spawnfunc_target_position where the laser ends -"mdl" - name of beam end effect to use -"beam_color" - color of the beam (default: red) -"dmg" - damage per second (-1 for a laser that kills immediately) -*/ - -void laser_setactive(entity this, int act) -{ - int old_status = this.active; - if(act == ACTIVE_TOGGLE) - { - if(this.active == ACTIVE_ACTIVE) - { - this.active = ACTIVE_NOT; - } - else - { - this.active = ACTIVE_ACTIVE; - } - } - else - { - this.active = act; - } - - if (this.active != old_status) - { - this.SendFlags |= SF_LASER_UPDATE_ACTIVE; - misc_laser_aim(this); - } -} - -void laser_use(entity this, entity actor, entity trigger) -{ - this.setactive(this, ACTIVE_TOGGLE); -} - -spawnfunc(misc_laser) -{ - if(this.mdl) - { - if(this.mdl == "none") - this.cnt = -1; - else - { - this.cnt = _particleeffectnum(this.mdl); - if(this.cnt < 0 && this.dmg) - this.cnt = particleeffectnum(EFFECT_LASER_DEADLY); - } - } - else if(!this.cnt) - { - if(this.dmg) - this.cnt = particleeffectnum(EFFECT_LASER_DEADLY); - else - this.cnt = -1; - } - if(this.cnt < 0) - this.cnt = -1; - - if(!this.beam_color && this.colormod) - { - LOG_WARN("misc_laser uses legacy field 'colormod', please use 'beam_color' instead"); - this.beam_color = this.colormod; - } - - if(this.beam_color == '0 0 0') - { - if(!this.alpha) - this.beam_color = '1 0 0'; - } - - if(this.message == "") - { - this.message = "saw the light"; - } - if (this.message2 == "") - { - this.message2 = "was pushed into a laser by"; - } - if(!this.scale) - { - this.scale = 1; - } - if(!this.modelscale) - { - this.modelscale = 1; - } - else if(this.modelscale < 0) - { - this.modelscale = 0; - } - setthink(this, misc_laser_think); - this.nextthink = time; - InitializeEntity(this, misc_laser_init, INITPRIO_FINDTARGET); - - this.mangle = this.angles; - - Net_LinkEntity(this, false, 0, laser_SendEntity); - - this.setactive = laser_setactive; - - IFTARGETED - { - // backwards compatibility - this.use = laser_use; - } - - this.reset = generic_netlinked_reset; - this.reset(this); -} -#elif defined(CSQC) - -// a laser goes from origin in direction angles -// it has color 'beam_color' -// and stops when something is in the way -entityclass(Laser); -classfield(Laser) .int cnt; // end effect -classfield(Laser) .vector colormod; -classfield(Laser) .int state; // on-off -classfield(Laser) .int count; // flags for the laser -classfield(Laser) .vector velocity; // laser endpoint if it is FINITE -classfield(Laser) .float alpha; -classfield(Laser) .float scale; // scaling factor of the thickness -classfield(Laser) .float modelscale; // scaling factor of the dlight - -void Draw_Laser(entity this) -{ - if(this.active == ACTIVE_NOT) - return; - InterpolateOrigin_Do(this); - if(this.count & SF_LASER_FINITE) - { - if(this.count & SF_LASER_NOTRACE) - { - trace_endpos = this.velocity; - trace_dphitq3surfaceflags = 0; - } - else - traceline(this.origin, this.velocity, 0, this); - } - else - { - if(this.count & SF_LASER_NOTRACE) - { - makevectors(this.angles); - trace_endpos = this.origin + v_forward * LASER_BEAM_MAXWORLDSIZE; - trace_dphitq3surfaceflags = Q3SURFACEFLAG_SKY; - } - else - { - makevectors(this.angles); - traceline(this.origin, this.origin + v_forward * LASER_BEAM_MAXLENGTH, 0, this); - if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY) - trace_endpos = this.origin + v_forward * LASER_BEAM_MAXWORLDSIZE; - } - } - if(this.scale != 0) - { - if(this.alpha) - { - Draw_CylindricLine(this.origin, trace_endpos, this.scale, "particles/laserbeam", 0, time * 3, this.beam_color, this.alpha, DRAWFLAG_NORMAL, view_origin); - } - else - { - Draw_CylindricLine(this.origin, trace_endpos, this.scale, "particles/laserbeam", 0, time * 3, this.beam_color, 0.5, DRAWFLAG_ADDITIVE, view_origin); - } - } - if (!(trace_dphitq3surfaceflags & (Q3SURFACEFLAG_SKY | Q3SURFACEFLAG_NOIMPACT))) - { - if(this.cnt >= 0) - __pointparticles(this.cnt, trace_endpos, trace_plane_normal, drawframetime * 1000); - if(this.beam_color != '0 0 0' && this.modelscale != 0) - adddynamiclight(trace_endpos + trace_plane_normal * 1, this.modelscale, this.beam_color * 5); - } -} - -NET_HANDLE(ENT_CLIENT_LASER, bool isnew) -{ - InterpolateOrigin_Undo(this); - - // 30 bytes, or 13 bytes for just moving - int sendflags = ReadByte(); - this.count = (sendflags & 0xF0); - - if(this.count & SF_LASER_FINITE) - this.iflags = IFLAG_VELOCITY | IFLAG_ORIGIN; - else - this.iflags = IFLAG_ANGLES | IFLAG_ORIGIN; - - if(sendflags & SF_LASER_UPDATE_ORIGIN) - { - this.origin = ReadVector(); - setorigin(this, this.origin); - } - if(sendflags & SF_LASER_UPDATE_EFFECT) - { - this.beam_color.x = ReadByte() / 255.0; - this.beam_color.y = ReadByte() / 255.0; - this.beam_color.z = ReadByte() / 255.0; - if(sendflags & SF_LASER_ALPHA) - this.alpha = ReadByte() / 255.0; - else - this.alpha = 0; - this.scale = 2; // NOTE: why 2? - this.modelscale = 50; // NOTE: why 50? - if(sendflags & SF_LASER_SCALE) - { - this.scale *= ReadByte() / 16.0; // beam radius - this.modelscale *= ReadByte() / 16.0; // dlight radius - } - if((sendflags & SF_LASER_FINITE) || !(sendflags & SF_LASER_NOTRACE)) - this.cnt = ReadShort(); // effect number - else - this.cnt = 0; - } - if(sendflags & SF_LASER_UPDATE_TARGET) - { - if(sendflags & SF_LASER_FINITE) - { - this.velocity = ReadVector(); - } - else - { - this.angles_x = ReadAngle(); - this.angles_y = ReadAngle(); - } - } - if(sendflags & SF_LASER_UPDATE_ACTIVE) - this.active = ReadByte(); - - return = true; - - InterpolateOrigin_Note(this); - this.draw = Draw_Laser; - if (isnew) IL_PUSH(g_drawables, this); -} -#endif diff --git a/qcsrc/common/triggers/misc/laser.qh b/qcsrc/common/triggers/misc/laser.qh deleted file mode 100644 index 0ff57646a..000000000 --- a/qcsrc/common/triggers/misc/laser.qh +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - - -const int LASER_FINITE = BIT(1); -const int LASER_NOTRACE = BIT(2); -const int LASER_INVERT_TEAM = BIT(3); - -const int SF_LASER_UPDATE_ORIGIN = BIT(0); -const int SF_LASER_UPDATE_TARGET = BIT(1); -const int SF_LASER_UPDATE_ACTIVE = BIT(2); -const int SF_LASER_UPDATE_EFFECT = BIT(3); - -const int SF_LASER_NOTRACE = BIT(4); -const int SF_LASER_SCALE = BIT(5); -const int SF_LASER_ALPHA = BIT(6); -const int SF_LASER_FINITE = BIT(7); - -.vector beam_color; - -const float LASER_BEAM_MAXLENGTH = 32768; // maximum length of a beam trace -// TODO: find a better way to do this -const float LASER_BEAM_MAXWORLDSIZE = 1048576; // to make sure the endpoint of the beam is not visible inside diff --git a/qcsrc/common/triggers/misc/teleport_dest.qc b/qcsrc/common/triggers/misc/teleport_dest.qc deleted file mode 100644 index 126a20ea2..000000000 --- a/qcsrc/common/triggers/misc/teleport_dest.qc +++ /dev/null @@ -1,89 +0,0 @@ -#include "teleport_dest.qh" -REGISTER_NET_LINKED(ENT_CLIENT_TELEPORT_DEST) - -#ifdef SVQC - -bool teleport_dest_send(entity this, entity to, int sendflags) -{ - WriteHeader(MSG_ENTITY, ENT_CLIENT_TELEPORT_DEST); - WriteByte(MSG_ENTITY, sendflags); - - if(sendflags & SF_TRIGGER_INIT) - { - WriteByte(MSG_ENTITY, this.cnt); - WriteCoord(MSG_ENTITY, this.speed); - WriteString(MSG_ENTITY, this.targetname); - WriteVector(MSG_ENTITY, this.origin); - - WriteAngle(MSG_ENTITY, this.mangle_x); - WriteAngle(MSG_ENTITY, this.mangle_y); - WriteAngle(MSG_ENTITY, this.mangle_z); - } - - return true; -} - -void teleport_dest_link(entity this) -{ - Net_LinkEntity(this, false, 0, teleport_dest_send); - this.SendFlags |= SF_TRIGGER_INIT; -} - -spawnfunc(info_teleport_destination) -{ - this.classname = "info_teleport_destination"; - - this.mangle = this.angles; - this.angles = '0 0 0'; - - //setorigin(this, this.origin + '0 0 27'); // To fix a mappers' habit as old as Quake - setorigin(this, this.origin); - - IFTARGETED - { - } - else - objerror (this, "^3Teleport destination without a targetname"); - - teleport_dest_link(this); -} - -spawnfunc(misc_teleporter_dest) -{ - spawnfunc_info_teleport_destination(this); -} - -#elif defined(CSQC) - -void teleport_dest_remove(entity this) -{ - // strfree(this.classname); - strfree(this.targetname); -} - -NET_HANDLE(ENT_CLIENT_TELEPORT_DEST, bool isnew) -{ - int sendflags = ReadByte(); - - if(sendflags & SF_TRIGGER_INIT) - { - this.classname = "info_teleport_destination"; - this.cnt = ReadByte(); - this.speed = ReadCoord(); - this.targetname = strzone(ReadString()); - this.origin = ReadVector(); - - this.mangle_x = ReadAngle(); - this.mangle_y = ReadAngle(); - this.mangle_z = ReadAngle(); - - setorigin(this, this.origin); - - this.drawmask = MASK_NORMAL; - this.entremove = teleport_dest_remove; - } - - return = true; -} - -#endif diff --git a/qcsrc/common/triggers/misc/teleport_dest.qh b/qcsrc/common/triggers/misc/teleport_dest.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/misc/teleport_dest.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/platforms.qc b/qcsrc/common/triggers/platforms.qc deleted file mode 100644 index 474787731..000000000 --- a/qcsrc/common/triggers/platforms.qc +++ /dev/null @@ -1,227 +0,0 @@ -#include "platforms.qh" -void generic_plat_blocked(entity this, entity blocker) -{ -#ifdef SVQC - if(this.dmg && blocker.takedamage != DAMAGE_NO) - { - if(this.dmgtime2 < time) - { - Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); - this.dmgtime2 = time + this.dmgtime; - } - - // Gib dead/dying stuff - if(IS_DEAD(blocker)) - Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); - } -#endif -} - -void plat_spawn_inside_trigger(entity this) -{ - entity trigger; - vector tmin, tmax; - - trigger = spawn(); - settouch(trigger, plat_center_touch); - set_movetype(trigger, MOVETYPE_NONE); - trigger.solid = SOLID_TRIGGER; - trigger.enemy = this; - - tmin = this.absmin + '25 25 0'; - tmax = this.absmax - '25 25 -8'; - tmin_z = tmax_z - (this.pos1_z - this.pos2_z + 8); - if (this.spawnflags & PLAT_LOW_TRIGGER) - tmax_z = tmin_z + 8; - - if (this.size_x <= 50) - { - tmin_x = (this.mins_x + this.maxs_x) / 2; - tmax_x = tmin_x + 1; - } - if (this.size_y <= 50) - { - tmin_y = (this.mins_y + this.maxs_y) / 2; - tmax_y = tmin_y + 1; - } - - if(tmin_x < tmax_x) - if(tmin_y < tmax_y) - if(tmin_z < tmax_z) - { - setsize (trigger, tmin, tmax); - return; - } - - // otherwise, something is fishy... - delete(trigger); - objerror(this, "plat_spawn_inside_trigger: platform has odd size or lip, can't spawn"); -} - -void plat_hit_top(entity this) -{ - _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM); - this.state = STATE_TOP; - - setthink(this, plat_go_down); - this.nextthink = this.ltime + 3; -} - -void plat_hit_bottom(entity this) -{ - _sound (this, CH_TRIGGER_SINGLE, this.noise1, VOL_BASE, ATTEN_NORM); - this.state = STATE_BOTTOM; -} - -void plat_go_down(entity this) -{ - _sound (this, CH_TRIGGER_SINGLE, this.noise, VOL_BASE, ATTEN_NORM); - this.state = STATE_DOWN; - SUB_CalcMove (this, this.pos2, TSPEED_LINEAR, this.speed, plat_hit_bottom); -} - -void plat_go_up(entity this) -{ - _sound (this, CH_TRIGGER_SINGLE, this.noise, VOL_BASE, ATTEN_NORM); - this.state = STATE_UP; - SUB_CalcMove (this, this.pos1, TSPEED_LINEAR, this.speed, plat_hit_top); -} - -void plat_center_touch(entity this, entity toucher) -{ -#ifdef SVQC - if (!toucher.iscreature) - return; - - if (toucher.health <= 0) - return; -#elif defined(CSQC) - if (!IS_PLAYER(toucher)) - return; - if(IS_DEAD(toucher)) - return; -#endif - - if (this.enemy.state == STATE_BOTTOM) { - plat_go_up(this.enemy); - } else if (this.enemy.state == STATE_TOP) - this.enemy.nextthink = this.enemy.ltime + 1; -} - -void plat_outside_touch(entity this, entity toucher) -{ -#ifdef SVQC - if (!toucher.iscreature) - return; - - if (toucher.health <= 0) - return; -#elif defined(CSQC) - if (!IS_PLAYER(toucher)) - return; -#endif - - if (this.enemy.state == STATE_TOP) { - entity e = this.enemy; - plat_go_down(e); - } -} - -void plat_trigger_use(entity this, entity actor, entity trigger) -{ - if (getthink(this)) - return; // already activated - plat_go_down(this); -} - - -void plat_crush(entity this, entity blocker) -{ - if((this.spawnflags & CRUSH) && (blocker.takedamage != DAMAGE_NO)) - { // KIll Kill Kill!! -#ifdef SVQC - Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); -#endif - } - else - { -#ifdef SVQC - if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) - { // Shall we bite? - Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); - // Gib dead/dying stuff - if(IS_DEAD(blocker)) - Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); - } -#endif - - if (this.state == STATE_UP) - plat_go_down (this); - else if (this.state == STATE_DOWN) - plat_go_up (this); - // when in other states, then the plat_crush event came delayed after - // plat state already had changed - // this isn't a bug per se! - } -} - -void plat_use(entity this, entity actor, entity trigger) -{ - this.use = func_null; - if (this.state != STATE_UP) - objerror (this, "plat_use: not in up state"); - plat_go_down(this); -} - -// WARNING: backwards compatibility because people don't use already existing fields :( -// TODO: Check if any maps use these fields and remove these fields if it doesn't break maps -.string sound1, sound2; - -void plat_reset(entity this) -{ - IFTARGETED - { - setorigin(this, this.pos1); - this.state = STATE_UP; - this.use = plat_use; - } - else - { - setorigin(this, this.pos2); - this.state = STATE_BOTTOM; - this.use = plat_trigger_use; - } - -#ifdef SVQC - this.SendFlags |= SF_TRIGGER_RESET; -#endif -} - -.float platmovetype_start_default, platmovetype_end_default; -bool set_platmovetype(entity e, string s) -{ - // sets platmovetype_start and platmovetype_end based on a string consisting of two values - - int n = tokenize_console(s); - if(n > 0) - e.platmovetype_start = stof(argv(0)); - else - e.platmovetype_start = 0; - - if(n > 1) - e.platmovetype_end = stof(argv(1)); - else - e.platmovetype_end = e.platmovetype_start; - - if(n > 2) - if(argv(2) == "force") - return true; // no checking, return immediately - - if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end)) - { - objerror(e, "Invalid platform move type; platform would go in reverse, which is not allowed."); - return false; - } - - return true; -} diff --git a/qcsrc/common/triggers/platforms.qh b/qcsrc/common/triggers/platforms.qh deleted file mode 100644 index 346cebc71..000000000 --- a/qcsrc/common/triggers/platforms.qh +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - - -const int PLAT_LOW_TRIGGER = BIT(0); - -.float dmgtime2; - -void plat_center_touch(entity this, entity toucher); -void plat_outside_touch(entity this, entity toucher); -void plat_trigger_use(entity this, entity actor, entity trigger); -void plat_go_up(entity this); -void plat_go_down(entity this); -void plat_crush(entity this, entity blocker); - -.float dmg; diff --git a/qcsrc/common/triggers/subs.qc b/qcsrc/common/triggers/subs.qc deleted file mode 100644 index 2a237fdcb..000000000 --- a/qcsrc/common/triggers/subs.qc +++ /dev/null @@ -1,369 +0,0 @@ -#include "subs.qh" -void SUB_NullThink(entity this) { } - -void SUB_CalcMoveDone(entity this); -void SUB_CalcAngleMoveDone(entity this); - -/* -================== -SUB_Friction - -Applies some friction to this -================== -*/ -.float friction; -void SUB_Friction (entity this) -{ - this.nextthink = time; - if(IS_ONGROUND(this)) - this.velocity = this.velocity * (1 - frametime * this.friction); -} - -/* -================== -SUB_VanishOrRemove - -Makes client invisible or removes non-client -================== -*/ -void SUB_VanishOrRemove (entity ent) -{ - if (IS_CLIENT(ent)) - { - // vanish - ent.alpha = -1; - ent.effects = 0; -#ifdef SVQC - ent.glow_size = 0; - ent.pflags = 0; -#endif - } - else - { - // remove - delete(ent); - } -} - -void SUB_SetFade_Think (entity this) -{ - if(this.alpha == 0) - this.alpha = 1; - setthink(this, SUB_SetFade_Think); - this.nextthink = time; - this.alpha -= frametime * this.fade_rate; - if (this.alpha < 0.01) - SUB_VanishOrRemove(this); - else - this.nextthink = time; -} - -/* -================== -SUB_SetFade - -Fade 'ent' out when time >= 'when' -================== -*/ -void SUB_SetFade (entity ent, float when, float fading_time) -{ - ent.fade_rate = 1/fading_time; - setthink(ent, SUB_SetFade_Think); - ent.nextthink = when; -} - -/* -============= -SUB_CalcMove - -calculate this.velocity and this.nextthink to reach dest from -this.origin traveling at speed -=============== -*/ -void SUB_CalcMoveDone(entity this) -{ - // After moving, set origin to exact final destination - - setorigin (this, this.finaldest); - this.velocity = '0 0 0'; - this.nextthink = -1; - if (this.think1 && this.think1 != SUB_CalcMoveDone) - this.think1 (this); -} - -.float platmovetype_turn; -void SUB_CalcMove_controller_think (entity this) -{ - float traveltime; - float phasepos; - float nexttick; - vector delta; - vector delta2; - vector veloc; - vector angloc; - vector nextpos; - delta = this.destvec; - delta2 = this.destvec2; - if(time < this.animstate_endtime) - { - nexttick = time + PHYS_INPUT_FRAMETIME; - - traveltime = this.animstate_endtime - this.animstate_starttime; - phasepos = (nexttick - this.animstate_starttime) / traveltime; // range: [0, 1] - phasepos = cubic_speedfunc(this.platmovetype_start, this.platmovetype_end, phasepos); - nextpos = this.origin + (delta * phasepos) + (delta2 * phasepos * phasepos); - // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning) - - if(this.owner.platmovetype_turn) - { - vector destangle; - destangle = delta + 2 * delta2 * phasepos; - destangle = vectoangles(destangle); - destangle_x = -destangle_x; // flip up / down orientation - - // take the shortest distance for the angles - vector v = this.owner.angles; - v.x -= 360 * floor((v.x - destangle_x) / 360 + 0.5); - v.y -= 360 * floor((v.y - destangle_y) / 360 + 0.5); - v.z -= 360 * floor((v.z - destangle_z) / 360 + 0.5); - this.owner.angles = v; - angloc = destangle - this.owner.angles; - angloc = angloc * (1 / PHYS_INPUT_FRAMETIME); // so it arrives for the next frame - this.owner.avelocity = angloc; - } - if(nexttick < this.animstate_endtime) - veloc = nextpos - this.owner.origin; - else - veloc = this.finaldest - this.owner.origin; - veloc = veloc * (1 / PHYS_INPUT_FRAMETIME); // so it arrives for the next frame - - this.owner.velocity = veloc; - this.nextthink = nexttick; - } - else - { - // derivative: delta + 2 * delta2 (e.g. for angle positioning) - entity own = this.owner; - setthink(own, this.think1); - // set the owner's reference to this entity to NULL - own.move_controller = NULL; - delete(this); - getthink(own)(own); - } -} - -void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector destin) -{ - // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + destin * t * t - // 2 * control * t - 2 * control * t * t + destin * t * t - // 2 * control * t + (destin - 2 * control) * t * t - - setorigin(controller, org); - control -= org; - destin -= org; - - controller.destvec = 2 * control; // control point - controller.destvec2 = destin - 2 * control; // quadratic part required to reach end point - // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (destin - control) -} - -void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector destin) -{ - // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + destin * t * t - // 2 * control * t - 2 * control * t * t + destin * t * t - // 2 * control * t + (destin - 2 * control) * t * t - - setorigin(controller, org); - destin -= org; - - controller.destvec = destin; // end point - controller.destvec2 = '0 0 0'; -} - -float TSPEED_TIME = -1; -float TSPEED_LINEAR = 0; -float TSPEED_START = 1; -float TSPEED_END = 2; -// TODO average too? - -void SUB_CalcMove_Bezier (entity this, vector tcontrol, vector tdest, float tspeedtype, float tspeed, void(entity this) func) -{ - float traveltime; - entity controller; - - if (!tspeed) - objerror (this, "No speed is defined!"); - - this.think1 = func; - this.finaldest = tdest; - setthink(this, SUB_CalcMoveDone); - - switch(tspeedtype) - { - default: - case TSPEED_START: - traveltime = 2 * vlen(tcontrol - this.origin) / tspeed; - break; - case TSPEED_END: - traveltime = 2 * vlen(tcontrol - tdest) / tspeed; - break; - case TSPEED_LINEAR: - traveltime = vlen(tdest - this.origin) / tspeed; - break; - case TSPEED_TIME: - traveltime = tspeed; - break; - } - - if (traveltime < 0.1) // useless anim - { - this.velocity = '0 0 0'; - this.nextthink = this.ltime + 0.1; - return; - } - - // delete the previous controller, otherwise changing movement midway is glitchy - if (this.move_controller != NULL) - { - delete(this.move_controller); - } - controller = new(SUB_CalcMove_controller); - controller.owner = this; - this.move_controller = controller; - controller.platmovetype = this.platmovetype; - controller.platmovetype_start = this.platmovetype_start; - controller.platmovetype_end = this.platmovetype_end; - SUB_CalcMove_controller_setbezier(controller, this.origin, tcontrol, tdest); - controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit. - controller.animstate_starttime = time; - controller.animstate_endtime = time + traveltime; - setthink(controller, SUB_CalcMove_controller_think); - controller.think1 = getthink(this); - - // the thinking is now done by the controller - setthink(this, SUB_NullThink); // for PushMove - this.nextthink = this.ltime + traveltime; - - // invoke controller - getthink(controller)(controller); -} - -void SUB_CalcMove (entity this, vector tdest, float tspeedtype, float tspeed, void(entity this) func) -{ - vector delta; - float traveltime; - - if (!tspeed) - objerror (this, "No speed is defined!"); - - this.think1 = func; - this.finaldest = tdest; - setthink(this, SUB_CalcMoveDone); - - if (tdest == this.origin) - { - this.velocity = '0 0 0'; - this.nextthink = this.ltime + 0.1; - return; - } - - delta = tdest - this.origin; - - switch(tspeedtype) - { - default: - case TSPEED_START: - case TSPEED_END: - case TSPEED_LINEAR: - traveltime = vlen (delta) / tspeed; - break; - case TSPEED_TIME: - traveltime = tspeed; - break; - } - - // Very short animations don't really show off the effect - // of controlled animation, so let's just use linear movement. - // Alternatively entities can choose to specify non-controlled movement. - // The only currently implemented alternative movement is linear (value 1) - if (traveltime < 0.15 || (this.platmovetype_start == 1 && this.platmovetype_end == 1)) // is this correct? - { - this.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division - this.nextthink = this.ltime + traveltime; - return; - } - - // now just run like a bezier curve... - SUB_CalcMove_Bezier(this, (this.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func); -} - -void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void(entity this) func) -{ - SUB_CalcMove(ent, tdest, tspeedtype, tspeed, func); -} - -/* -============= -SUB_CalcAngleMove - -calculate this.avelocity and this.nextthink to reach destangle from -this.angles rotating - -The calling function should make sure this.setthink is valid -=============== -*/ -void SUB_CalcAngleMoveDone(entity this) -{ - // After rotating, set angle to exact final angle - this.angles = this.finalangle; - this.avelocity = '0 0 0'; - this.nextthink = -1; - if (this.think1 && this.think1 != SUB_CalcAngleMoveDone) // avoid endless loops - this.think1 (this); -} - -// FIXME: I fixed this function only for rotation around the main axes -void SUB_CalcAngleMove (entity this, vector destangle, float tspeedtype, float tspeed, void(entity this) func) -{ - if (!tspeed) - objerror (this, "No speed is defined!"); - - // take the shortest distance for the angles - this.angles_x -= 360 * floor((this.angles_x - destangle_x) / 360 + 0.5); - this.angles_y -= 360 * floor((this.angles_y - destangle_y) / 360 + 0.5); - this.angles_z -= 360 * floor((this.angles_z - destangle_z) / 360 + 0.5); - vector delta = destangle - this.angles; - float traveltime; - - switch(tspeedtype) - { - default: - case TSPEED_START: - case TSPEED_END: - case TSPEED_LINEAR: - traveltime = vlen (delta) / tspeed; - break; - case TSPEED_TIME: - traveltime = tspeed; - break; - } - - this.think1 = func; - this.finalangle = destangle; - setthink(this, SUB_CalcAngleMoveDone); - - if (traveltime < 0.1) - { - this.avelocity = '0 0 0'; - this.nextthink = this.ltime + 0.1; - return; - } - - this.avelocity = delta * (1 / traveltime); - this.nextthink = this.ltime + traveltime; -} - -void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void(entity this) func) -{ - SUB_CalcAngleMove (ent, destangle, tspeedtype, tspeed, func); -} diff --git a/qcsrc/common/triggers/subs.qh b/qcsrc/common/triggers/subs.qh deleted file mode 100644 index 8d4e40650..000000000 --- a/qcsrc/common/triggers/subs.qh +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once -#include "defs.qh" - -void SUB_SetFade (entity ent, float when, float fading_time); -void SUB_VanishOrRemove (entity ent); - -.vector finaldest, finalangle; //plat.qc stuff -.void(entity this) think1; -.float state; -.float t_length, t_width; - -.vector destvec; -.vector destvec2; - -.float delay; -.float wait; -.float lip; -.float speed; -.float sounds; -.string platmovetype; -.float platmovetype_start, platmovetype_end; - -//entity activator; - -.string killtarget; - -.vector pos1, pos2; -.vector mangle; - -.string target2; -.string target3; -.string target4; -.string curvetarget; -.float target_random; -.float trigger_reverse; - -// Keys player is holding -.float itemkeys; -// message delay for func_door locked by keys and key locks -// this field is used on player entities -.float key_door_messagetime; - -.vector dest1, dest2; - -.entity move_controller; - -#ifdef CSQC -// this stuff is defined in the server side engine VM, so we must define it separately here -.float takedamage; -const int DAMAGE_NO = 0; -const int DAMAGE_YES = 1; -const int DAMAGE_AIM = 2; - -.string noise, noise1, noise2, noise3; // contains names of wavs to play - -.float max_health; // players maximum health is stored here -#endif diff --git a/qcsrc/common/triggers/target/_mod.inc b/qcsrc/common/triggers/target/_mod.inc deleted file mode 100644 index afd1050b9..000000000 --- a/qcsrc/common/triggers/target/_mod.inc +++ /dev/null @@ -1,11 +0,0 @@ -// generated file; do not modify -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include diff --git a/qcsrc/common/triggers/target/_mod.qh b/qcsrc/common/triggers/target/_mod.qh deleted file mode 100644 index 7c9fbeec5..000000000 --- a/qcsrc/common/triggers/target/_mod.qh +++ /dev/null @@ -1,11 +0,0 @@ -// generated file; do not modify -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include diff --git a/qcsrc/common/triggers/target/changelevel.qc b/qcsrc/common/triggers/target/changelevel.qc deleted file mode 100644 index 114fd8718..000000000 --- a/qcsrc/common/triggers/target/changelevel.qc +++ /dev/null @@ -1,55 +0,0 @@ -#include "changelevel.qh" -#ifdef SVQC -.string chmap, gametype; -.entity chlevel_targ; - -void target_changelevel_use(entity this, entity actor, entity trigger) -{ - if(this.spawnflags & CHANGELEVEL_MULTIPLAYER) - { - // simply don't react if a non-player triggers it - if(!IS_PLAYER(actor)) { return; } - - actor.chlevel_targ = this; - - int plnum = 0; - int realplnum = 0; - // let's not count bots - FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { - ++realplnum; - if(it.chlevel_targ == this) - ++plnum; - }); - if(plnum < ceil(realplnum * min(1, this.count))) // 70% of players - return; - } - - if(this.gametype != "") - MapInfo_SwitchGameType(MapInfo_Type_FromString(this.gametype)); - - if (this.chmap == "") - localcmd("endmatch\n"); - else - localcmd(strcat("changelevel ", this.chmap, "\n")); -} - -/*target_changelevel -Target to change/end level -KEYS: -chmap: map to switch to, leave empty for endmatch -gametype: gametype for the next map -count: fraction of real players that need to trigger this entity for levelchange -SPAWNFLAGS: -CHANGELEVEL_MULTIPLAYER: multiplayer support -*/ - -spawnfunc(target_changelevel) -{ - this.use = target_changelevel_use; - - if(!this.count) - { - this.count = 0.7; - } -} -#endif diff --git a/qcsrc/common/triggers/target/changelevel.qh b/qcsrc/common/triggers/target/changelevel.qh deleted file mode 100644 index f6e206edc..000000000 --- a/qcsrc/common/triggers/target/changelevel.qh +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - - -const int CHANGELEVEL_MULTIPLAYER = BIT(1); diff --git a/qcsrc/common/triggers/target/include.qc b/qcsrc/common/triggers/target/include.qc deleted file mode 100644 index a45c65ed0..000000000 --- a/qcsrc/common/triggers/target/include.qc +++ /dev/null @@ -1,11 +0,0 @@ -#include "include.qh" - -#include "changelevel.qc" -#include "kill.qc" -#include "levelwarp.qc" -#include "location.qc" -#include "music.qc" -#include "spawn.qc" -#include "spawnpoint.qc" -#include "speaker.qc" -#include "voicescript.qc" diff --git a/qcsrc/common/triggers/target/include.qh b/qcsrc/common/triggers/target/include.qh deleted file mode 100644 index c0f7cad44..000000000 --- a/qcsrc/common/triggers/target/include.qh +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -#include "music.qh" diff --git a/qcsrc/common/triggers/target/kill.qc b/qcsrc/common/triggers/target/kill.qc deleted file mode 100644 index 2751c600e..000000000 --- a/qcsrc/common/triggers/target/kill.qc +++ /dev/null @@ -1,26 +0,0 @@ -#include "kill.qh" -#include "location.qh" -#ifdef SVQC - -void target_kill_use(entity this, entity actor, entity trigger) -{ - if(actor.takedamage == DAMAGE_NO) - return; - - if(!actor.iscreature && !actor.damagedbytriggers) - return; - - Damage(actor, this, trigger, 1000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, actor.origin, '0 0 0'); -} - -spawnfunc(target_kill) -{ - this.classname = "target_kill"; - - if (this.message == "") - this.message = "was in the wrong place"; - - this.use = target_kill_use; -} - -#endif diff --git a/qcsrc/common/triggers/target/kill.qh b/qcsrc/common/triggers/target/kill.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/target/kill.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/target/levelwarp.qc b/qcsrc/common/triggers/target/levelwarp.qc deleted file mode 100644 index 21419cf81..000000000 --- a/qcsrc/common/triggers/target/levelwarp.qc +++ /dev/null @@ -1,21 +0,0 @@ -#include "levelwarp.qh" - -#ifdef SVQC -void target_levelwarp_use(entity this, entity actor, entity trigger) -{ - if(!autocvar_g_campaign) - return; // only in campaign - - if(this.cnt) - CampaignLevelWarp(this.cnt - 1); // specific level - else - CampaignLevelWarp(-1); // next level -} - -spawnfunc(target_levelwarp) -{ - // this.cnt is index (starting from 1) of the campaign level to warp to - // 0 means next level - this.use = target_levelwarp_use; -} -#endif diff --git a/qcsrc/common/triggers/target/levelwarp.qh b/qcsrc/common/triggers/target/levelwarp.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/target/levelwarp.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/target/location.qc b/qcsrc/common/triggers/target/location.qc deleted file mode 100644 index 5774f45f9..000000000 --- a/qcsrc/common/triggers/target/location.qc +++ /dev/null @@ -1,25 +0,0 @@ -#include "location.qh" -#ifdef SVQC -void target_push_init(entity this); - -spawnfunc(target_location) -{ - this.classname = "target_location"; - // location name in netname - // eventually support: count, teamgame selectors, line of sight? - - target_push_init(this); - - IL_PUSH(g_locations, this); -} - -spawnfunc(info_location) -{ - this.classname = "target_location"; - this.message = this.netname; - - target_push_init(this); - - IL_PUSH(g_locations, this); -} -#endif diff --git a/qcsrc/common/triggers/target/location.qh b/qcsrc/common/triggers/target/location.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/target/location.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/target/music.qc b/qcsrc/common/triggers/target/music.qc deleted file mode 100644 index 5a63872db..000000000 --- a/qcsrc/common/triggers/target/music.qc +++ /dev/null @@ -1,344 +0,0 @@ -#include "music.qh" -#if defined(CSQC) -#elif defined(MENUQC) -#elif defined(SVQC) - #include - #include - #include - #include -#endif - -REGISTER_NET_TEMP(TE_CSQC_TARGET_MUSIC) -REGISTER_NET_LINKED(ENT_CLIENT_TRIGGER_MUSIC) - -#ifdef SVQC - -IntrusiveList g_targetmusic_list; -STATIC_INIT(g_targetmusic_list) -{ - g_targetmusic_list = IL_NEW(); -} - -// values: -// volume -// noise -// targetname -// lifetime -// fade_time -// fade_rate -// when triggered, the music is overridden for activator until lifetime (or forever, if lifetime is 0) -// when targetname is not set, THIS ONE is default -void target_music_sendto(entity this, int to, bool is) -{ - WriteHeader(to, TE_CSQC_TARGET_MUSIC); - WriteShort(to, etof(this)); - WriteByte(to, this.volume * 255.0 * is); - WriteByte(to, this.fade_time * 16.0); - WriteByte(to, this.fade_rate * 16.0); - WriteByte(to, this.lifetime); - WriteString(to, this.noise); -} -void target_music_reset(entity this) -{ - if (this.targetname == "") - { - target_music_sendto(this, MSG_ALL, true); - } -} -void target_music_kill() -{ - IL_EACH(g_targetmusic_list, true, - { - it.volume = 0; - if (it.targetname == "") - target_music_sendto(it, MSG_ALL, true); - else - target_music_sendto(it, MSG_ALL, false); - }); -} -void target_music_use(entity this, entity actor, entity trigger) -{ - if(!actor) - return; - if(IS_REAL_CLIENT(actor)) - { - msg_entity = actor; - target_music_sendto(this, MSG_ONE, true); - } - FOREACH_CLIENT(IS_SPEC(it) && it.enemy == actor, { - msg_entity = it; - target_music_sendto(this, MSG_ONE, true); - }); -} -spawnfunc(target_music) -{ - this.use = target_music_use; - this.reset = target_music_reset; - if(!this.volume) - this.volume = 1; - IL_PUSH(g_targetmusic_list, this); - if(this.targetname == "") - target_music_sendto(this, MSG_INIT, true); - else - target_music_sendto(this, MSG_INIT, false); -} -void TargetMusic_RestoreGame() -{ - IL_EACH(g_targetmusic_list, true, - { - if(it.targetname == "") - target_music_sendto(it, MSG_INIT, true); - else - target_music_sendto(it, MSG_INIT, false); - }); -} -// values: -// volume -// noise -// targetname -// fade_time -// spawnflags: -// START_DISABLED -// can be disabled/enabled for everyone with relays -bool trigger_music_SendEntity(entity this, entity to, int sendflags) -{ - WriteHeader(MSG_ENTITY, ENT_CLIENT_TRIGGER_MUSIC); - WriteByte(MSG_ENTITY, sendflags); - if(sendflags & SF_MUSIC_ORIGIN) - { - WriteVector(MSG_ENTITY, this.origin); - } - if(sendflags & SF_TRIGGER_INIT) - { - if(this.model != "null") - { - WriteShort(MSG_ENTITY, this.modelindex); - WriteVector(MSG_ENTITY, this.mins); - WriteVector(MSG_ENTITY, this.maxs); - } - else - { - WriteShort(MSG_ENTITY, 0); - WriteVector(MSG_ENTITY, this.maxs); - } - WriteByte(MSG_ENTITY, this.volume * 255.0); - WriteByte(MSG_ENTITY, this.fade_time * 16.0); - WriteByte(MSG_ENTITY, this.fade_rate * 16.0); - WriteString(MSG_ENTITY, this.noise); - } - if(sendflags & SF_TRIGGER_UPDATE) - { - WriteByte(MSG_ENTITY, this.active); - } - return true; -} -void trigger_music_reset(entity this) -{ - if(this.spawnflags & START_DISABLED) - { - this.setactive(this, ACTIVE_NOT); - } - else - { - this.setactive(this, ACTIVE_ACTIVE); - } -} - -spawnfunc(trigger_music) -{ - if(this.model != "") - { - _setmodel(this, this.model); - } - if(!this.volume) - { - this.volume = 1; - } - if(!this.modelindex) - { - setorigin(this, this.origin + this.mins); - setsize(this, '0 0 0', this.maxs - this.mins); - } - - this.setactive = generic_netlinked_setactive; - this.use = generic_netlinked_legacy_use; // backwards compatibility - this.reset = trigger_music_reset; - this.reset(this); - - Net_LinkEntity(this, false, 0, trigger_music_SendEntity); -} -#elif defined(CSQC) - -entity TargetMusic_list; -STATIC_INIT(TargetMusic_list) -{ - TargetMusic_list = LL_NEW(); -} - -void TargetMusic_Advance() -{ - // run AFTER all the thinks! - entity best = music_default; - if (music_target && time < music_target.lifetime) - { - best = music_target; - } - if (music_trigger) - { - best = music_trigger; - } - LL_EACH(TargetMusic_list, it.noise, { - const float vol0 = (getsoundtime(it, CH_BGM_SINGLE) >= 0) ? it.lastvol : -1; - if (it == best) - { - // increase volume - it.state = (it.fade_time > 0) ? bound(0, it.state + frametime / it.fade_time, 1) : 1; - } - else - { - // decrease volume - it.state = (it.fade_rate > 0) ? bound(0, it.state - frametime / it.fade_rate, 1) : 0; - } - const float vol = it.state * it.volume * autocvar_bgmvolume; - if (vol != vol0) - { - if(vol0 < 0) - sound7(it, CH_BGM_SINGLE, it.noise, vol, ATTEN_NONE, 0, BIT(4)); // restart - else - sound7(it, CH_BGM_SINGLE, "", vol, ATTEN_NONE, 0, BIT(4)); - it.lastvol = vol; - } - }); - music_trigger = NULL; - bgmtime = (best) ? getsoundtime(best, CH_BGM_SINGLE) : gettime(GETTIME_CDTRACK); -} - -NET_HANDLE(TE_CSQC_TARGET_MUSIC, bool isNew) -{ - Net_TargetMusic(); - return true; -} - -void Net_TargetMusic() -{ - const int id = ReadShort(); - const float vol = ReadByte() / 255.0; - const float fai = ReadByte() / 16.0; - const float fao = ReadByte() / 16.0; - const float tim = ReadByte(); - const string noi = ReadString(); - - entity e = NULL; - LL_EACH(TargetMusic_list, it.count == id, { e = it; break; }); - if (!e) - { - LL_PUSH(TargetMusic_list, e = new_pure(TargetMusic)); - e.count = id; - } - if(e.noise != noi) - { - strcpy(e.noise, noi); - precache_sound(e.noise); - _sound(e, CH_BGM_SINGLE, e.noise, 0, ATTEN_NONE); - if(getsoundtime(e, CH_BGM_SINGLE) < 0) - { - LOG_TRACEF("Cannot initialize sound %s", e.noise); - strfree(e.noise); - } - } - e.volume = vol; - e.fade_time = fai; - e.fade_rate = fao; - if(vol > 0) - { - if(tim == 0) - { - music_default = e; - if(!music_disabled) - { - e.state = 2; - cvar_settemp("music_playlist_index", "-1"); // don't use playlists - localcmd("cd stop\n"); // just in case - music_disabled = 1; - } - } - else - { - music_target = e; - e.lifetime = time + tim; - } - } -} - -void Ent_TriggerMusic_Think(entity this) -{ - if(this.active == ACTIVE_NOT) - { - return; - } - vector org = (csqcplayer) ? csqcplayer.origin : view_origin; - if(WarpZoneLib_BoxTouchesBrush(org + STAT(PL_MIN), org + STAT(PL_MAX), this, NULL)) - { - music_trigger = this; - } -} - -void Ent_TriggerMusic_Remove(entity this) -{ - strfree(this.noise); -} - -NET_HANDLE(ENT_CLIENT_TRIGGER_MUSIC, bool isnew) -{ - int sendflags = ReadByte(); - if(sendflags & SF_MUSIC_ORIGIN) - { - this.origin = ReadVector(); - } - if(sendflags & SF_TRIGGER_INIT) - { - this.modelindex = ReadShort(); - if(this.modelindex) - { - this.mins = ReadVector(); - this.maxs = ReadVector(); - } - else - { - this.mins = '0 0 0'; - this.maxs = ReadVector(); - } - - this.volume = ReadByte() / 255.0; - this.fade_time = ReadByte() / 16.0; - this.fade_rate = ReadByte() / 16.0; - string s = this.noise; - strcpy(this.noise, ReadString()); - if(this.noise != s) - { - precache_sound(this.noise); - sound7(this, CH_BGM_SINGLE, this.noise, 0, ATTEN_NONE, 0, BIT(4)); - if(getsoundtime(this, CH_BGM_SINGLE) < 0) - { - LOG_WARNF("Cannot initialize sound %s", this.noise); - strfree(this.noise); - } - } - } - if(sendflags & SF_TRIGGER_UPDATE) - { - this.active = ReadByte(); - } - - setorigin(this, this.origin); - setsize(this, this.mins, this.maxs); - this.draw = Ent_TriggerMusic_Think; - if(isnew) - { - LL_PUSH(TargetMusic_list, this); - IL_PUSH(g_drawables, this); - } - return true; -} - -#endif diff --git a/qcsrc/common/triggers/target/music.qh b/qcsrc/common/triggers/target/music.qh deleted file mode 100644 index ccf3f674e..000000000 --- a/qcsrc/common/triggers/target/music.qh +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -.float lifetime; - -const int SF_MUSIC_ORIGIN = BIT(2); - -#ifdef CSQC -float music_disabled; -entity music_default; -entity music_target; -entity music_trigger; -// FIXME also control bgmvolume here, to not require a target_music for the default track. - -entityclass(TargetMusic); -classfield(TargetMusic) .int state; -classfield(TargetMusic) .float lastvol; - -void TargetMusic_Advance(); - -void Net_TargetMusic(); - -void Ent_TriggerMusic_Think(entity this); - -void Ent_TriggerMusic_Remove(entity this); - -#elif defined(SVQC) -void target_music_kill(); -#endif diff --git a/qcsrc/common/triggers/target/spawn.qc b/qcsrc/common/triggers/target/spawn.qc deleted file mode 100644 index 9c999ed4d..000000000 --- a/qcsrc/common/triggers/target/spawn.qc +++ /dev/null @@ -1,340 +0,0 @@ -#include "spawn.qh" -#if defined(CSQC) -#elif defined(MENUQC) -#elif defined(SVQC) - #include - #include -#endif - -#ifdef SVQC - -// spawner entity -// "classname" "target_spawn" -// "message" "fieldname value fieldname value ..." -// "spawnflags" -// ON_MAPLOAD = trigger on map load - -float target_spawn_initialized; -.void(entity this) target_spawn_spawnfunc; -float target_spawn_spawnfunc_field; -.entity target_spawn_activator; -.float target_spawn_id; -float target_spawn_count; - -void target_spawn_helper_setmodel(entity this) -{ - _setmodel(this, this.model); -} - -void target_spawn_helper_setsize(entity this) -{ - setsize(this, this.mins, this.maxs); -} - -void target_spawn_edit_entity(entity this, entity e, string msg, entity kt, entity t2, entity t3, entity t4, entity act, entity trigger) -{ - float i, n, valuefieldpos; - string key, value, valuefield, valueoffset, valueoffsetrandom; - entity valueent; - vector data, data2; - - n = tokenize_console(msg); - - for(i = 0; i < n-1; i += 2) - { - key = argv(i); - value = argv(i+1); - if(key == "$") - { - data.x = -1; - data.y = FIELD_STRING; - } - else - { - data = stov(db_get(TemporaryDB, strcat("/target_spawn/field/", key))); - if(data.y == 0) // undefined field, i.e., invalid type - { - LOG_INFO("target_spawn: invalid/unknown entity key ", key, " specified, ignored!"); - continue; - } - } - if(substring(value, 0, 1) == "$") - { - value = substring(value, 1, strlen(value) - 1); - if(substring(value, 0, 1) == "$") - { - // deferred replacement - // do nothing - // useful for creating target_spawns with this! - } - else - { - // replace me! - valuefieldpos = strstrofs(value, "+", 0); - valueoffset = ""; - if(valuefieldpos != -1) - { - valueoffset = substring(value, valuefieldpos + 1, strlen(value) - valuefieldpos - 1); - value = substring(value, 0, valuefieldpos); - } - - valuefieldpos = strstrofs(valueoffset, "+", 0); - valueoffsetrandom = ""; - if(valuefieldpos != -1) - { - valueoffsetrandom = substring(valueoffset, valuefieldpos + 1, strlen(valueoffset) - valuefieldpos - 1); - valueoffset = substring(valueoffset, 0, valuefieldpos); - } - - valuefieldpos = strstrofs(value, ".", 0); - valuefield = ""; - if(valuefieldpos != -1) - { - valuefield = substring(value, valuefieldpos + 1, strlen(value) - valuefieldpos - 1); - value = substring(value, 0, valuefieldpos); - } - - if(value == "self") - { - valueent = this; - value = ""; - } - else if(value == "activator") - { - valueent = act; - value = ""; - } - else if(value == "other") - { - valueent = trigger; - value = ""; - } - else if(value == "pusher") - { - if(time < act.pushltime) - valueent = act.pusher; - else - valueent = NULL; - value = ""; - } - else if(value == "target") - { - valueent = e; - value = ""; - } - else if(value == "killtarget") - { - valueent = kt; - value = ""; - } - else if(value == "target2") - { - valueent = t2; - value = ""; - } - else if(value == "target3") - { - valueent = t3; - value = ""; - } - else if(value == "target4") - { - valueent = t4; - value = ""; - } - else if(value == "time") - { - valueent = NULL; - value = ftos(time); - } - else - { - LOG_INFO("target_spawn: invalid/unknown variable replacement ", value, " specified, ignored!"); - continue; - } - - if(valuefield == "") - { - if(value == "") - value = ftos(etof(valueent)); - } - else - { - if(value != "") - { - LOG_INFO("target_spawn: try to get a field of a non-entity, ignored!"); - continue; - } - data2 = stov(db_get(TemporaryDB, strcat("/target_spawn/field/", valuefield))); - if(data2_y == 0) // undefined field, i.e., invalid type - { - LOG_INFO("target_spawn: invalid/unknown entity key replacement ", valuefield, " specified, ignored!"); - continue; - } - value = getentityfieldstring(data2_x, valueent); - } - - if(valueoffset != "") - { - switch(data.y) - { - case FIELD_STRING: - value = strcat(value, valueoffset); - break; - case FIELD_FLOAT: - value = ftos(stof(value) + stof(valueoffset)); - break; - case FIELD_VECTOR: - value = vtos(stov(value) + stov(valueoffset)); - break; - default: - LOG_INFO("target_spawn: only string, float and vector fields can do calculations, calculation ignored!"); - break; - } - } - - if(valueoffsetrandom != "") - { - switch(data.y) - { - case FIELD_FLOAT: - value = ftos(stof(value) + random() * stof(valueoffsetrandom)); - break; - case FIELD_VECTOR: - data2 = stov(valueoffsetrandom); - value = vtos(stov(value) + random() * data2_x * '1 0 0' + random() * data2_y * '0 1 0' + random() * data2_z * '0 0 1'); - break; - default: - LOG_INFO("target_spawn: only float and vector fields can do random calculations, calculation ignored!"); - break; - } - } - } - } - if(key == "$") - { - if(substring(value, 0, 1) == "_") - value = strcat("target_spawn_helper", value); - putentityfieldstring(target_spawn_spawnfunc_field, e, value); - - e.target_spawn_spawnfunc(e); - - // We called an external function, so we have to re-tokenize msg. - n = tokenize_console(msg); - } - else - { - if(data.y == FIELD_VECTOR) - value = strreplace("'", "", value); // why?!? - putentityfieldstring(data.x, e, value); - } - } -} - -void target_spawn_useon(entity e, entity this, entity actor, entity trigger) -{ - this.target_spawn_activator = actor; - target_spawn_edit_entity( - this, - e, - this.message, - find(NULL, targetname, this.killtarget), - find(NULL, targetname, this.target2), - find(NULL, targetname, this.target3), - find(NULL, targetname, this.target4), - actor, - trigger - ); -} - -bool target_spawn_cancreate(entity this) -{ - float c; - entity e; - - c = this.count; - if(c == 0) // no limit? - return true; - - ++c; // increase count to not include MYSELF - for(e = NULL; (e = findfloat(e, target_spawn_id, this.target_spawn_id)); --c) - ; - - // if c now is 0, we have AT LEAST the given count (maybe more), so don't spawn any more - if(c == 0) - return false; - return true; -} - -void target_spawn_use(entity this, entity actor, entity trigger) -{ - if(this.target == "") - { - // spawn new entity - if(!target_spawn_cancreate(this)) - return; - entity e = spawn(); - e.spawnfunc_checked = true; - target_spawn_useon(e, this, actor, trigger); - e.target_spawn_id = this.target_spawn_id; - } - else if(this.target == "*activator") - { - // edit entity - if(actor) - target_spawn_useon(actor, this, actor, trigger); - } - else - { - // edit entity - FOREACH_ENTITY_STRING(targetname, this.target, - { - target_spawn_useon(it, this, actor, trigger); - }); - } -} - -void target_spawn_spawnfirst(entity this) -{ - entity act = this.target_spawn_activator; - if(this.spawnflags & ON_MAPLOAD) - target_spawn_use(this, act, NULL); -} - -void initialize_field_db() -{ - if(!target_spawn_initialized) - { - float n, i; - string fn; - vector prev, next; - float ft; - - n = numentityfields(); - for(i = 0; i < n; ++i) - { - fn = entityfieldname(i); - ft = entityfieldtype(i); - next = i * '1 0 0' + ft * '0 1 0' + '0 0 1'; - prev = stov(db_get(TemporaryDB, strcat("/target_spawn/field/", fn))); - if(prev.y == 0) - { - db_put(TemporaryDB, strcat("/target_spawn/field/", fn), vtos(next)); - if(fn == "target_spawn_spawnfunc") - target_spawn_spawnfunc_field = i; - } - } - - target_spawn_initialized = 1; - } -} - -spawnfunc(target_spawn) -{ - initialize_field_db(); - this.use = target_spawn_use; - this.message = strzone(strreplace("'", "\"", this.message)); - this.target_spawn_id = ++target_spawn_count; - InitializeEntity(this, target_spawn_spawnfirst, INITPRIO_LAST); -} -#endif diff --git a/qcsrc/common/triggers/target/spawn.qh b/qcsrc/common/triggers/target/spawn.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/target/spawn.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/target/spawnpoint.qc b/qcsrc/common/triggers/target/spawnpoint.qc deleted file mode 100644 index fe1538551..000000000 --- a/qcsrc/common/triggers/target/spawnpoint.qc +++ /dev/null @@ -1,24 +0,0 @@ -#include "spawnpoint.qh" - -#ifdef SVQC -void target_spawnpoint_use(entity this, entity actor, entity trigger) -{ - if(this.active != ACTIVE_ACTIVE) - return; - - actor.spawnpoint_targ = this; -} - -void target_spawnpoint_reset(entity this) -{ - this.active = ACTIVE_ACTIVE; -} - -// TODO: persistent spawnflag? -spawnfunc(target_spawnpoint) -{ - this.active = ACTIVE_ACTIVE; - this.use = target_spawnpoint_use; - this.reset = target_spawnpoint_reset; -} -#endif diff --git a/qcsrc/common/triggers/target/spawnpoint.qh b/qcsrc/common/triggers/target/spawnpoint.qh deleted file mode 100644 index 2eeb8da62..000000000 --- a/qcsrc/common/triggers/target/spawnpoint.qh +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#ifdef SVQC -.entity spawnpoint_targ; -#endif diff --git a/qcsrc/common/triggers/target/speaker.qc b/qcsrc/common/triggers/target/speaker.qc deleted file mode 100644 index 11c9ad7ba..000000000 --- a/qcsrc/common/triggers/target/speaker.qc +++ /dev/null @@ -1,138 +0,0 @@ -#include "speaker.qh" -#ifdef SVQC -// TODO add a way to do looped sounds with sound(); then complete this entity -void target_speaker_use_off(entity this, entity actor, entity trigger); -void target_speaker_use_activator(entity this, entity actor, entity trigger) -{ - if (!IS_REAL_CLIENT(actor)) - return; - string snd; - if(substring(this.noise, 0, 1) == "*") - { - var .string sample = GetVoiceMessageSampleField(substring(this.noise, 1, -1)); - if(GetPlayerSoundSampleField_notFound) - snd = SND(Null); - else if(actor.(sample) == "") - snd = SND(Null); - else - { - tokenize_console(actor.(sample)); - float n; - n = stof(argv(1)); - if(n > 0) - snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization - else - snd = strcat(argv(0), ".wav"); // randomization - } - } - else - snd = this.noise; - msg_entity = actor; - soundto(MSG_ONE, this, CH_TRIGGER, snd, VOL_BASE * this.volume, this.atten); -} -void target_speaker_use_on(entity this, entity actor, entity trigger) -{ - string snd; - if(substring(this.noise, 0, 1) == "*") - { - var .string sample = GetVoiceMessageSampleField(substring(this.noise, 1, -1)); - if(GetPlayerSoundSampleField_notFound) - snd = SND(Null); - else if(actor.(sample) == "") - snd = SND(Null); - else - { - tokenize_console(actor.(sample)); - float n; - n = stof(argv(1)); - if(n > 0) - snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization - else - snd = strcat(argv(0), ".wav"); // randomization - } - } - else - snd = this.noise; - _sound(this, CH_TRIGGER_SINGLE, snd, VOL_BASE * this.volume, this.atten); - if(this.spawnflags & (SPEAKER_LOOPED_ON + SPEAKER_LOOPED_OFF)) - this.use = target_speaker_use_off; -} -void target_speaker_use_off(entity this, entity actor, entity trigger) -{ - sound(this, CH_TRIGGER_SINGLE, SND_Null, VOL_BASE * this.volume, this.atten); - this.use = target_speaker_use_on; -} -void target_speaker_reset(entity this) -{ - if(this.spawnflags & SPEAKER_LOOPED_ON) - { - if(this.use == target_speaker_use_on) - target_speaker_use_on(this, NULL, NULL); - } - else if(this.spawnflags & SPEAKER_LOOPED_OFF) - { - if(this.use == target_speaker_use_off) - target_speaker_use_off(this, NULL, NULL); - } -} - -spawnfunc(target_speaker) -{ - // TODO: "*" prefix to sound file name - // TODO: wait and random (just, HOW? random is not a field) - if(this.noise) - precache_sound (this.noise); - - if(!this.atten && (this.spawnflags & SPEAKER_GLOBAL)) - { - LOG_WARN("target_speaker uses legacy spawnflag GLOBAL (BIT(2)), please set atten to -1 instead"); - this.atten = -1; - } - - if(!this.atten) - { - IFTARGETED - this.atten = ATTEN_NORM; - else - this.atten = ATTEN_STATIC; - } - else if(this.atten < 0) - this.atten = 0; - - if(!this.volume) - this.volume = 1; - - IFTARGETED - { - if(this.spawnflags & SPEAKER_ACTIVATOR) - this.use = target_speaker_use_activator; - else if(this.spawnflags & SPEAKER_LOOPED_ON) - { - target_speaker_use_on(this, NULL, NULL); - this.reset = target_speaker_reset; - } - else if(this.spawnflags & SPEAKER_LOOPED_OFF) - { - this.use = target_speaker_use_on; - this.reset = target_speaker_reset; - } - else - this.use = target_speaker_use_on; - } - else if(this.spawnflags & SPEAKER_LOOPED_ON) - { - ambientsound (this.origin, this.noise, VOL_BASE * this.volume, this.atten); - delete(this); - } - else if(this.spawnflags & SPEAKER_LOOPED_OFF) - { - objerror(this, "This sound entity can never be activated"); - } - else - { - // Quake/Nexuiz fallback - ambientsound (this.origin, this.noise, VOL_BASE * this.volume, this.atten); - delete(this); - } -} -#endif diff --git a/qcsrc/common/triggers/target/speaker.qh b/qcsrc/common/triggers/target/speaker.qh deleted file mode 100644 index 53e0194be..000000000 --- a/qcsrc/common/triggers/target/speaker.qh +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - - -const int SPEAKER_LOOPED_ON = BIT(0); -const int SPEAKER_LOOPED_OFF = BIT(1); -const int SPEAKER_GLOBAL = BIT(2); // legacy, set speaker atten to -1 instead -const int SPEAKER_ACTIVATOR = BIT(3); diff --git a/qcsrc/common/triggers/target/voicescript.qc b/qcsrc/common/triggers/target/voicescript.qc deleted file mode 100644 index 6dfb568a8..000000000 --- a/qcsrc/common/triggers/target/voicescript.qc +++ /dev/null @@ -1,102 +0,0 @@ -#include "voicescript.qh" -#ifdef SVQC -.entity voicescript; // attached voice script -.float voicescript_index; // index of next voice, or -1 to use the randomized ones -.float voicescript_nextthink; // time to play next voice -.float voicescript_voiceend; // time when this voice ends - -void target_voicescript_clear(entity pl) -{ - pl.voicescript = NULL; -} - -void target_voicescript_use(entity this, entity actor, entity trigger) -{ - if(actor.voicescript != this) - { - actor.voicescript = this; - actor.voicescript_index = 0; - actor.voicescript_nextthink = time + this.delay; - } -} - -void target_voicescript_next(entity pl) -{ - entity vs; - float i, n, dt; - - vs = pl.voicescript; - if(!vs) - return; - if(vs.message == "") - return; - if (!IS_PLAYER(pl)) - return; - if(game_stopped) - return; - - if(time >= pl.voicescript_voiceend) - { - if(time >= pl.voicescript_nextthink) - { - // get the next voice... - n = tokenize_console(vs.message); - - if(pl.voicescript_index < vs.cnt) - i = pl.voicescript_index * 2; - else if(n > vs.cnt * 2) - i = ((pl.voicescript_index - vs.cnt) % ((n - vs.cnt * 2 - 1) / 2)) * 2 + vs.cnt * 2 + 1; - else - i = -1; - - if(i >= 0) - { - play2(pl, strcat(vs.netname, "/", argv(i), ".wav")); - dt = stof(argv(i + 1)); - if(dt >= 0) - { - pl.voicescript_voiceend = time + dt; - pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random()); - } - else - { - pl.voicescript_voiceend = time - dt; - pl.voicescript_nextthink = pl.voicescript_voiceend; - } - - pl.voicescript_index += 1; - } - else - { - pl.voicescript = NULL; // stop trying then - } - } - } -} - -spawnfunc(target_voicescript) -{ - // netname: directory of the sound files - // message: list of "sound file" duration "sound file" duration, a *, and again a list - // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7 - // Here, a - in front of the duration means that no delay is to be - // added after this message - // wait: average time between messages - // delay: initial delay before the first message - - float i, n; - this.use = target_voicescript_use; - - n = tokenize_console(this.message); - this.cnt = n / 2; - for(i = 0; i+1 < n; i += 2) - { - if(argv(i) == "*") - { - this.cnt = i / 2; - ++i; - } - precache_sound(strcat(this.netname, "/", argv(i), ".wav")); - } -} -#endif diff --git a/qcsrc/common/triggers/target/voicescript.qh b/qcsrc/common/triggers/target/voicescript.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/target/voicescript.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/teleporters.qc b/qcsrc/common/triggers/teleporters.qc deleted file mode 100644 index 25626e01b..000000000 --- a/qcsrc/common/triggers/teleporters.qc +++ /dev/null @@ -1,315 +0,0 @@ -#include "teleporters.qh" - -#if defined(CSQC) -#elif defined(MENUQC) -#elif defined(SVQC) - #include - #include - #include - #include "../constants.qh" - #include "../triggers/subs.qh" - #include "../util.qh" - #include - #include - #include - #include - #include "../deathtypes/all.qh" - #include "../turrets/sv_turrets.qh" - #include "../vehicles/all.qh" - #include "../mapinfo.qh" - #include -#endif - -#ifdef SVQC -float check_tdeath(entity player, vector org, vector telefragmin, vector telefragmax) -{ - if (IS_PLAYER(player) && !IS_DEAD(player)) - { - TDEATHLOOP(org) - { - #ifdef SVQC - if (!(teamplay && autocvar_g_telefrags_teamplay && head.team == player.team)) - #endif - if(IS_PLAYER(head)) - if(!IS_DEAD(head)) - return 1; - } - } - return 0; -} - -void trigger_teleport_link(entity this); - -void tdeath(entity player, entity teleporter, entity telefragger, vector telefragmin, vector telefragmax) -{ - TDEATHLOOP(player.origin) - { - if (IS_PLAYER(player) && player.health >= 1) - { - if (!(teamplay && autocvar_g_telefrags_teamplay && head.team == player.team)) - { - if(IS_PLAYER(head)) - if(head.health >= 1) - ++tdeath_hit; - Damage (head, teleporter, telefragger, 10000, DEATH_TELEFRAG.m_id, DMG_NOWEP, head.origin, '0 0 0'); - } - } - else // dead bodies and monsters gib themselves instead of telefragging - Damage (telefragger, teleporter, telefragger, 10000, DEATH_TELEFRAG.m_id, DMG_NOWEP, telefragger.origin, '0 0 0'); - } -} - -void spawn_tdeath(vector v0, entity e, vector v) -{ - tdeath(e, e, e, '0 0 0', '0 0 0'); -} -#endif - -void TeleportPlayer(entity teleporter, entity player, vector to, vector to_angles, vector to_velocity, vector telefragmin, vector telefragmax, float tflags) -{ - entity telefragger; - vector from; - - if(teleporter.owner) - telefragger = teleporter.owner; - else - telefragger = player; - - makevectors (to_angles); - -#ifdef SVQC - if(player.teleportable == TELEPORT_NORMAL) // don't play sounds or show particles for anything that isn't a player, maybe change later to block only observers - { - if(teleporter.pushltime < time) // only show one teleport effect per teleporter per 0.2 seconds, for better fps - { - if(tflags & TELEPORT_FLAG_SOUND) - { - string thesound = SND(TELEPORT); - if(teleporter.noise != "") - { - RandomSelection_Init(); - FOREACH_WORD(teleporter.noise, true, - { - RandomSelection_AddString(it, 1, 1); - }); - thesound = RandomSelection_chosen_string; - } - _sound (player, CH_TRIGGER, thesound, VOL_BASE, ATTEN_NORM); - } - if(tflags & TELEPORT_FLAG_PARTICLES) - { - Send_Effect(EFFECT_TELEPORT, player.origin, '0 0 0', 1); - Send_Effect(EFFECT_TELEPORT, to + v_forward * 32, '0 0 0', 1); - } - teleporter.pushltime = time + 0.2; - } - } -#endif - - // Relocate the player - // assuming to allows PL_MIN to PL_MAX box and some more -#ifdef SVQC - from = player.origin; - setorigin(player, to); - player.oldorigin = to; // don't undo the teleport by unsticking - player.angles = to_angles; - player.fixangle = true; - player.velocity = to_velocity; - BITXOR_ASSIGN(player.effects, EF_TELEPORT_BIT); - - makevectors(player.angles); - Reset_ArcBeam(player, v_forward); - UpdateCSQCProjectileAfterTeleport(player); - UpdateItemAfterTeleport(player); -#elif defined(CSQC) - from = player.origin; - setorigin(player, to); - player.angles = to_angles; - player.velocity = to_velocity; - UNSET_ONGROUND(player); - player.iflags |= IFLAG_TELEPORTED | IFLAG_V_ANGLE | IFLAG_ANGLES; - player.csqcmodel_teleported = 1; - player.v_angle = to_angles; - - if(player == csqcplayer) // not for anything but the main player - { - setproperty(VF_ANGLES, player.angles); - setproperty(VF_CL_VIEWANGLES, player.angles); - } -#endif - -#ifdef SVQC - if(IS_PLAYER(player)) - { - if(tflags & TELEPORT_FLAG_TDEATH) - if(player.takedamage && !IS_DEAD(player) && !g_race && !g_cts && (autocvar_g_telefrags || (tflags & TELEPORT_FLAG_FORCE_TDEATH))) - tdeath(player, teleporter, telefragger, telefragmin, telefragmax); - - // player no longer is on ground - UNSET_ONGROUND(player); - - // reset tracking of oldvelocity for impact damage (sudden velocity changes) - player.oldvelocity = player.velocity; - - // reset tracking of who pushed you into a hazard (for kill credit) - if(teleporter.owner) - { - player.pusher = teleporter.owner; - player.pushltime = time + autocvar_g_maxpushtime; - player.istypefrag = PHYS_INPUT_BUTTON_CHAT(player); - } - else - { - player.pushltime = 0; - player.istypefrag = 0; - } - - player.lastteleporttime = time; - player.lastteleport_origin = from; - } -#endif -} - -entity Simple_TeleportPlayer(entity teleporter, entity player) -{ - vector locout; - entity e = NULL; - - // Find the output teleporter - if(teleporter.enemy) - { - e = teleporter.enemy; - } - else - { - // sorry CSQC, random stuff ain't gonna happen -#ifdef SVQC - RandomSelection_Init(); - FOREACH_ENTITY_STRING(targetname, teleporter.target, - { - bool p = true; - if(STAT(TELEPORT_TELEFRAG_AVOID, player)) - { - #ifdef SVQC - locout = it.origin + '0 0 1' * (1 - player.mins.z - 24); - #elif defined(CSQC) - locout = it.origin + '0 0 1' * (1 - player.mins.z - 24); - #endif - if(check_tdeath(player, locout, '0 0 0', '0 0 0')) - p = false; - } - RandomSelection_AddEnt(it, (it.cnt ? it.cnt : 1), p); - }); - e = RandomSelection_chosen_ent; -#endif - } - -#ifdef SVQC - if(!e) { sprint(player, "Teleport destination vanished. Sorry... please complain to the mapper.\n"); } -#elif defined(CSQC) - if(!e) { LOG_INFO("Teleport destination could not be found from CSQC."); } -#endif - - makevectors(e.mangle); - - if(e.speed) - if(vdist(player.velocity, >, e.speed)) - player.velocity = normalize(player.velocity) * max(0, e.speed); - - if(STAT(TELEPORT_MAXSPEED, player)) - if(vdist(player.velocity, >, STAT(TELEPORT_MAXSPEED, player))) - player.velocity = normalize(player.velocity) * max(0, STAT(TELEPORT_MAXSPEED, player)); - - locout = e.origin + '0 0 1' * (1 - player.mins.z - 24); - - TeleportPlayer(teleporter, player, locout, e.mangle, v_forward * vlen(player.velocity), '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER); - - return e; -} - -void teleport_findtarget(entity this) -{ - bool istrigger = (this.solid == SOLID_TRIGGER); - - int n = 0; - for(entity e = NULL; (e = find(e, targetname, this.target)); ) - { - ++n; -#ifdef SVQC - if(e.move_movetype == MOVETYPE_NONE) - { - entity tracetest_ent = spawn(); - setsize(tracetest_ent, PL_MIN_CONST, PL_MAX_CONST); - tracetest_ent.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; - waypoint_spawnforteleporter(this, e.origin, 0, tracetest_ent); - delete(tracetest_ent); - } - if(e.classname != "info_teleport_destination") - LOG_INFO("^3MAPPER ERROR: teleporter does target an invalid teleport destination entity. Angles will not work."); -#endif - } - - if(n == 0) - { - // no dest! - objerror (this, "Teleporter with nonexistant target"); - return; - } - else if(n == 1) - { - // exactly one dest - bots love that - this.enemy = find(NULL, targetname, this.target); - } - else - { - // have to use random selection every single time - this.enemy = NULL; - } - - // now enable touch - if(istrigger) - settouch(this, Teleport_Touch); -#ifdef SVQC - if(istrigger) - trigger_teleport_link(this); -#endif -} - -entity Teleport_Find(vector mi, vector ma) -{ - IL_EACH(g_teleporters, WarpZoneLib_BoxTouchesBrush(mi, ma, it, NULL), - { - return it; - }); - return NULL; -} - -void WarpZone_PostTeleportPlayer_Callback(entity pl) -{ -#ifdef SVQC - makevectors(pl.angles); - Reset_ArcBeam(pl, v_forward); - UpdateCSQCProjectileAfterTeleport(pl); - UpdateItemAfterTeleport(pl); - if (IS_PLAYER(pl)) anticheat_fixangle(pl); -#endif - // "disown" projectiles after teleport - if(pl.owner) - if(pl.owner == pl.realowner) - { - #ifdef SVQC - if(!(pl.flags & FL_PROJECTILE)) - #elif defined(CSQC) - if(!(pl.flags & BIT(15))) // FL_PROJECTILE - #endif - LOG_INFO("A non-projectile got through a warpzone and its owner cleared. It's a ", pl.classname, "."); - pl.owner = NULL; - } - if(IS_PLAYER(pl)) - { - // reset tracking of oldvelocity for impact damage (sudden velocity changes) - #ifdef SVQC - pl.oldvelocity = pl.velocity; - #endif - } -} diff --git a/qcsrc/common/triggers/teleporters.qh b/qcsrc/common/triggers/teleporters.qh deleted file mode 100644 index 68c5114f4..000000000 --- a/qcsrc/common/triggers/teleporters.qh +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once -#include "defs.qh" - -IntrusiveList g_teleporters; -STATIC_INIT(g_teleporters) { g_teleporters = IL_NEW(); } - -.entity pusher; - -const int TELEPORT_FLAG_SOUND = BIT(0); -const int TELEPORT_FLAG_PARTICLES = BIT(1); -const int TELEPORT_FLAG_TDEATH = BIT(2); -const int TELEPORT_FLAG_FORCE_TDEATH = BIT(3); - -#define TELEPORT_FLAGS_WARPZONE 0 -#define TELEPORT_FLAGS_PORTAL (TELEPORT_FLAG_SOUND | TELEPORT_FLAG_PARTICLES | TELEPORT_FLAG_TDEATH | TELEPORT_FLAG_FORCE_TDEATH) -#define TELEPORT_FLAGS_TELEPORTER (TELEPORT_FLAG_SOUND | TELEPORT_FLAG_PARTICLES | TELEPORT_FLAG_TDEATH) - -// types for .teleportable entity setting -const int TELEPORT_NORMAL = 1; // play sounds/effects etc -const int TELEPORT_SIMPLE = 2; // only do teleport, nothing special - -entity Simple_TeleportPlayer(entity teleporter, entity player); - -void Teleport_Touch(entity this, entity toucher); - -void teleport_findtarget(entity this); - -entity Teleport_Find(vector mi, vector ma); - -void TeleportPlayer(entity teleporter, entity player, vector to, vector to_angles, vector to_velocity, vector telefragmin, vector telefragmax, float tflags); - -#ifdef SVQC - -void trigger_teleport_use(entity this, entity actor, entity trigger); - -#define TDEATHLOOP(o) \ - entity head; \ - vector deathmin; \ - vector deathmax; \ - float deathradius; \ - deathmin = (o) + player.mins; \ - deathmax = (o) + player.maxs; \ - if(telefragmin != telefragmax) \ - { \ - if(deathmin.x > telefragmin.x) deathmin.x = telefragmin.x; \ - if(deathmin.y > telefragmin.y) deathmin.y = telefragmin.y; \ - if(deathmin.z > telefragmin.z) deathmin.z = telefragmin.z; \ - if(deathmax.x < telefragmax.x) deathmax.x = telefragmax.x; \ - if(deathmax.y < telefragmax.y) deathmax.y = telefragmax.y; \ - if(deathmax.z < telefragmax.z) deathmax.z = telefragmax.z; \ - } \ - deathradius = max(vlen(deathmin), vlen(deathmax)); \ - for(head = findradius(o, deathradius); head; head = head.chain) \ - if(head != player) \ - if(head.takedamage) \ - if(boxesoverlap(deathmin, deathmax, head.absmin, head.absmax)) - -float check_tdeath(entity player, vector org, vector telefragmin, vector telefragmax); -float tdeath_hit; -void tdeath(entity player, entity teleporter, entity telefragger, vector telefragmin, vector telefragmax); - -void spawn_tdeath(vector v0, entity e, vector v); - -void Reset_ArcBeam(entity player, vector forward); - -#endif - -void WarpZone_PostTeleportPlayer_Callback(entity pl); - -#ifdef CSQC -.entity realowner; -#endif diff --git a/qcsrc/common/triggers/trigger/_mod.inc b/qcsrc/common/triggers/trigger/_mod.inc deleted file mode 100644 index 05a496eb3..000000000 --- a/qcsrc/common/triggers/trigger/_mod.inc +++ /dev/null @@ -1,25 +0,0 @@ -// generated file; do not modify -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include diff --git a/qcsrc/common/triggers/trigger/_mod.qh b/qcsrc/common/triggers/trigger/_mod.qh deleted file mode 100644 index 2c7477b1d..000000000 --- a/qcsrc/common/triggers/trigger/_mod.qh +++ /dev/null @@ -1,25 +0,0 @@ -// generated file; do not modify -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include diff --git a/qcsrc/common/triggers/trigger/counter.qc b/qcsrc/common/triggers/trigger/counter.qc deleted file mode 100644 index 4c89c4c27..000000000 --- a/qcsrc/common/triggers/trigger/counter.qc +++ /dev/null @@ -1,67 +0,0 @@ -#include "counter.qh" -#ifdef SVQC -void counter_reset(entity this); - -void counter_use(entity this, entity actor, entity trigger) -{ - this.count -= 1; - if (this.count < 0) - return; - - bool doactivate = (this.spawnflags & COUNTER_FIRE_AT_COUNT); - - if (this.count == 0) - { - if(IS_PLAYER(actor) && !(this.spawnflags & SPAWNFLAG_NOMESSAGE)) - Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COMPLETED); - - doactivate = true; - - if(this.respawntime) - { - setthink(this, counter_reset); - this.nextthink = time + this.respawntime; - } - } - else - { - if(IS_PLAYER(actor) && !(this.spawnflags & SPAWNFLAG_NOMESSAGE)) - { - if(this.count >= 4) - Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COUNTER); - else - Send_Notification(NOTIF_ONE, actor, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, this.count); - } - } - - if(doactivate) - SUB_UseTargets(this, actor, trigger); -} - -void counter_reset(entity this) -{ - setthink(this, func_null); - this.nextthink = 0; - this.count = this.cnt; -} - -/*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage COUNTER_FIRE_AT_COUNT -Acts as an intermediary for an action that takes multiple inputs. - -If nomessage is not set, it will print "1 more.. " etc when triggered and "sequence complete" when finished. -If COUNTER_FIRE_AT_COUNT is set, it will also fire all of its targets at countdown, making it behave like trigger_mulitple with limited shots - -If respawntime is set, it will re-enable itself after the time once the sequence has been completed - -After the counter has been triggered "count" times (default 2), it will fire all of its targets. -*/ -spawnfunc(trigger_counter) -{ - if (!this.count) - this.count = 2; - this.cnt = this.count; - - this.use = counter_use; - this.reset = counter_reset; -} -#endif diff --git a/qcsrc/common/triggers/trigger/counter.qh b/qcsrc/common/triggers/trigger/counter.qh deleted file mode 100644 index 394d15472..000000000 --- a/qcsrc/common/triggers/trigger/counter.qh +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - - -const int COUNTER_FIRE_AT_COUNT = BIT(2); diff --git a/qcsrc/common/triggers/trigger/delay.qc b/qcsrc/common/triggers/trigger/delay.qc deleted file mode 100644 index 2cd4cfd13..000000000 --- a/qcsrc/common/triggers/trigger/delay.qc +++ /dev/null @@ -1,32 +0,0 @@ -#include "delay.qh" -#ifdef SVQC -void delay_delayeduse(entity this) -{ - SUB_UseTargets(this, this.enemy, this.goalentity); - this.enemy = this.goalentity = NULL; -} - -void delay_use(entity this, entity actor, entity trigger) -{ - this.enemy = actor; - this.goalentity = trigger; - setthink(this, delay_delayeduse); - this.nextthink = time + this.wait; -} - -void delay_reset(entity this) -{ - this.enemy = this.goalentity = NULL; - setthink(this, func_null); - this.nextthink = 0; -} - -spawnfunc(trigger_delay) -{ - if(!this.wait) - this.wait = 1; - - this.use = delay_use; - this.reset = delay_reset; -} -#endif diff --git a/qcsrc/common/triggers/trigger/delay.qh b/qcsrc/common/triggers/trigger/delay.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/trigger/delay.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/trigger/disablerelay.qc b/qcsrc/common/triggers/trigger/disablerelay.qc deleted file mode 100644 index eee61c993..000000000 --- a/qcsrc/common/triggers/trigger/disablerelay.qc +++ /dev/null @@ -1,29 +0,0 @@ -#include "disablerelay.qh" -#ifdef SVQC -void trigger_disablerelay_use(entity this, entity actor, entity trigger) -{ - int a = 0, b = 0; - - for(entity e = NULL; (e = find(e, targetname, this.target)); ) - { - if(e.use == SUB_UseTargets) - { - e.use = SUB_DontUseTargets; - ++a; - } - else if(e.use == SUB_DontUseTargets) - { - e.use = SUB_UseTargets; - ++b; - } - } - - if((!a) == (!b)) - LOG_INFO("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!"); -} - -spawnfunc(trigger_disablerelay) -{ - this.use = trigger_disablerelay_use; -} -#endif diff --git a/qcsrc/common/triggers/trigger/disablerelay.qh b/qcsrc/common/triggers/trigger/disablerelay.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/trigger/disablerelay.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/trigger/flipflop.qc b/qcsrc/common/triggers/trigger/flipflop.qc deleted file mode 100644 index 141f3ea9f..000000000 --- a/qcsrc/common/triggers/trigger/flipflop.qc +++ /dev/null @@ -1,23 +0,0 @@ -#include "flipflop.qh" -#ifdef SVQC -/*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED -"Flip-flop" trigger gate... lets only every second trigger event through -*/ -void flipflop_use(entity this, entity actor, entity trigger) -{ - this.state = !this.state; - if(this.state) - SUB_UseTargets(this, actor, trigger); -} - -spawnfunc(trigger_flipflop) -{ - if(this.spawnflags & START_ENABLED) - { - this.state = true; - } - this.use = flipflop_use; - this.reset = spawnfunc_trigger_flipflop; // perfect resetter -} - -#endif diff --git a/qcsrc/common/triggers/trigger/flipflop.qh b/qcsrc/common/triggers/trigger/flipflop.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/trigger/flipflop.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/trigger/gamestart.qc b/qcsrc/common/triggers/trigger/gamestart.qc deleted file mode 100644 index 72d76d183..000000000 --- a/qcsrc/common/triggers/trigger/gamestart.qc +++ /dev/null @@ -1,28 +0,0 @@ -#include "gamestart.qh" -#ifdef SVQC -void gamestart_use(entity this, entity actor, entity trigger) -{ - SUB_UseTargets(this, this, trigger); - delete(this); -} - -void gamestart_use_this(entity this) -{ - gamestart_use(this, NULL, NULL); -} - -spawnfunc(trigger_gamestart) -{ - this.use = gamestart_use; - this.reset2 = spawnfunc_trigger_gamestart; - - if(this.wait) - { - setthink(this, adaptor_think2use); - this.nextthink = game_starttime + this.wait; - } - else - InitializeEntity(this, gamestart_use_this, INITPRIO_FINDTARGET); -} - -#endif diff --git a/qcsrc/common/triggers/trigger/gamestart.qh b/qcsrc/common/triggers/trigger/gamestart.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/trigger/gamestart.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/trigger/gravity.qc b/qcsrc/common/triggers/trigger/gravity.qc deleted file mode 100644 index 1ac0f8768..000000000 --- a/qcsrc/common/triggers/trigger/gravity.qc +++ /dev/null @@ -1,111 +0,0 @@ -#include "gravity.qh" -#ifdef SVQC -.entity trigger_gravity_check; -void trigger_gravity_remove(entity own) -{ - if(own.trigger_gravity_check.owner == own) - { - UpdateCSQCProjectile(own); - own.gravity = own.trigger_gravity_check.gravity; - delete(own.trigger_gravity_check); - } - else - backtrace("Removing a trigger_gravity_check with no valid owner"); - own.trigger_gravity_check = NULL; -} -void trigger_gravity_check_think(entity this) -{ - // This spawns when a player enters the gravity zone and checks if he left. - // Each frame, this.count is set to 2 by trigger_gravity_touch() and decreased by 1 here. - // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that. - if(this.count <= 0) - { - if(this.owner.trigger_gravity_check == this) - trigger_gravity_remove(this.owner); - else - delete(this); - return; - } - else - { - this.count -= 1; - this.nextthink = time; - } -} - -// legacy -void trigger_gravity_use(entity this, entity actor, entity trigger) -{ - this.setactive(this, ACTIVE_TOGGLE); -} - -void trigger_gravity_touch(entity this, entity toucher) -{ - float g; - - if(this.active == ACTIVE_NOT) - return; - - EXACTTRIGGER_TOUCH(this, toucher); - - g = this.gravity; - - if (!(this.spawnflags & GRAVITY_STICKY)) - { - if(toucher.trigger_gravity_check) - { - if(this == toucher.trigger_gravity_check.enemy) - { - // same? - // NOTE: see explanation in trigger_gravity_check_think - toucher.trigger_gravity_check.count = 2; // gravity one more frame... - return; - } - - // compare prio - if(this.cnt > toucher.trigger_gravity_check.enemy.cnt) - trigger_gravity_remove(toucher); - else - return; - } - toucher.trigger_gravity_check = spawn(); - toucher.trigger_gravity_check.enemy = this; - toucher.trigger_gravity_check.owner = toucher; - toucher.trigger_gravity_check.gravity = toucher.gravity; - setthink(toucher.trigger_gravity_check, trigger_gravity_check_think); - toucher.trigger_gravity_check.nextthink = time; - toucher.trigger_gravity_check.count = 2; - if(toucher.gravity) - g *= toucher.gravity; - } - - if (toucher.gravity != g) - { - toucher.gravity = g; - if(this.noise != "") - _sound (toucher, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM); - UpdateCSQCProjectile(this.owner); - } -} - -spawnfunc(trigger_gravity) -{ - if(this.gravity == 1) - return; - - EXACTTRIGGER_INIT; - settouch(this, trigger_gravity_touch); - if(this.noise != "") - precache_sound(this.noise); - - this.active = ACTIVE_ACTIVE; - this.setactive = generic_setactive; - IFTARGETED - { - // legacy use - this.use = trigger_gravity_use; - if(this.spawnflags & GRAVITY_START_DISABLED) - this.active = ACTIVE_NOT; - } -} -#endif diff --git a/qcsrc/common/triggers/trigger/gravity.qh b/qcsrc/common/triggers/trigger/gravity.qh deleted file mode 100644 index 872f04ad9..000000000 --- a/qcsrc/common/triggers/trigger/gravity.qh +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - - -const int GRAVITY_STICKY = BIT(0); // keep gravity multiplier even after exiting the trigger_gravity -const int GRAVITY_START_DISABLED = BIT(1); diff --git a/qcsrc/common/triggers/trigger/heal.qc b/qcsrc/common/triggers/trigger/heal.qc deleted file mode 100644 index cfcd726fc..000000000 --- a/qcsrc/common/triggers/trigger/heal.qc +++ /dev/null @@ -1,65 +0,0 @@ -#include "heal.qh" -#ifdef SVQC -.float triggerhealtime; -void trigger_heal_touch(entity this, entity toucher) -{ - if (this.active != ACTIVE_ACTIVE) - return; - - // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu) - if (toucher.iscreature) - { - if (toucher.takedamage && !IS_DEAD(toucher) && toucher.triggerhealtime < time) - { - bool is_trigger = this.targetname == ""; - if(is_trigger) - EXACTTRIGGER_TOUCH(this, toucher); - if(this.delay > 0) - toucher.triggerhealtime = time + this.delay; - - bool playthesound = (this.spawnflags & HEAL_SOUND_ALWAYS); - if (toucher.health < this.max_health) - { - playthesound = true; - toucher.health = min(toucher.health + this.health, this.max_health); - toucher.pauserothealth_finished = max(toucher.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot); - } - - if(playthesound) - _sound (toucher, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM); - } - } -} - -void trigger_heal_use(entity this, entity actor, entity trigger) -{ - trigger_heal_touch(this, actor); -} - -void trigger_heal_init(entity this) -{ - this.active = ACTIVE_ACTIVE; - if(!this.delay) - this.delay = 1; - if(!this.health) - this.health = 10; - if(!this.max_health) - this.max_health = 200; // max health topoff for field - if(this.noise == "") - this.noise = "misc/mediumhealth.wav"; - precache_sound(this.noise); -} - -spawnfunc(trigger_heal) -{ - EXACTTRIGGER_INIT; - settouch(this, trigger_heal_touch); - trigger_heal_init(this); -} - -spawnfunc(target_heal) -{ - this.use = trigger_heal_use; - trigger_heal_init(this); -} -#endif diff --git a/qcsrc/common/triggers/trigger/heal.qh b/qcsrc/common/triggers/trigger/heal.qh deleted file mode 100644 index 8dbeea545..000000000 --- a/qcsrc/common/triggers/trigger/heal.qh +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - - -const int HEAL_SOUND_ALWAYS = BIT(2); diff --git a/qcsrc/common/triggers/trigger/hurt.qc b/qcsrc/common/triggers/trigger/hurt.qc deleted file mode 100644 index 966e0cfb0..000000000 --- a/qcsrc/common/triggers/trigger/hurt.qc +++ /dev/null @@ -1,93 +0,0 @@ -#include "hurt.qh" -#ifdef SVQC -void trigger_hurt_use(entity this, entity actor, entity trigger) -{ - if(IS_PLAYER(actor)) - this.enemy = actor; - else - this.enemy = NULL; // let's just destroy it, if taking over is too much work -} - -.float triggerhurttime; -void trigger_hurt_touch(entity this, entity toucher) -{ - if (this.active != ACTIVE_ACTIVE) - return; - - if(this.team) - if(((this.spawnflags & INVERT_TEAMS) == 0) == (this.team != toucher.team)) - return; - - // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu) - if (toucher.iscreature) - { - if (toucher.takedamage) - if (toucher.triggerhurttime < time) - { - EXACTTRIGGER_TOUCH(this, toucher); - toucher.triggerhurttime = time + 1; - - entity own; - own = this.enemy; - if (!IS_PLAYER(own)) - { - own = this; - this.enemy = NULL; // I still hate you all - } - - Damage (toucher, this, own, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, toucher.origin, '0 0 0'); - } - } - else if(toucher.damagedbytriggers) - { - if(toucher.takedamage) - { - EXACTTRIGGER_TOUCH(this, toucher); - Damage(toucher, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, toucher.origin, '0 0 0'); - } - } - - return; -} - -/*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ? -Any object touching this will be hurt -set dmg to damage amount -default dmg = 1000 -*/ -.entity trigger_hurt_next; -entity trigger_hurt_last; -entity trigger_hurt_first; -spawnfunc(trigger_hurt) -{ - EXACTTRIGGER_INIT; - this.active = ACTIVE_ACTIVE; - settouch(this, trigger_hurt_touch); - this.use = trigger_hurt_use; - this.enemy = world; // I hate you all - if (!this.dmg) - this.dmg = 1000; - if (this.message == "") - this.message = "was in the wrong place"; - if (this.message2 == "") - this.message2 = "was thrown into a world of hurt by"; - // this.message = "someone like %s always gets wrongplaced"; - - if(!trigger_hurt_first) - trigger_hurt_first = this; - if(trigger_hurt_last) - trigger_hurt_last.trigger_hurt_next = this; - trigger_hurt_last = this; -} - -bool tracebox_hits_trigger_hurt(vector start, vector e_min, vector e_max, vector end) -{ - entity th; - - for(th = trigger_hurt_first; th; th = th.trigger_hurt_next) - if(tracebox_hits_box(start, e_min, e_max, end, th.absmin, th.absmax)) - return true; - - return false; -} -#endif diff --git a/qcsrc/common/triggers/trigger/hurt.qh b/qcsrc/common/triggers/trigger/hurt.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/trigger/hurt.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/trigger/impulse.qc b/qcsrc/common/triggers/trigger/impulse.qc deleted file mode 100644 index c4e7ae287..000000000 --- a/qcsrc/common/triggers/trigger/impulse.qc +++ /dev/null @@ -1,243 +0,0 @@ -#include "impulse.qh" -// targeted (directional) mode -void trigger_impulse_touch_directional(entity this, entity toucher) -{ - entity targ; - float pushdeltatime; - float str; - - if (this.active != ACTIVE_ACTIVE) - return; - - if (!isPushable(toucher)) - return; - - EXACTTRIGGER_TOUCH(this, toucher); - - targ = find(NULL, targetname, this.target); - if(!targ) - { - objerror(this, "trigger_force without a (valid) .target!\n"); - delete(this); - return; - } - - // falloff is not supported because radius is always 0 in directional mode - str = this.strength; - - pushdeltatime = time - toucher.lastpushtime; - if (pushdeltatime > IMPULSE_MAX_PUSHDELTATIME) - { - pushdeltatime = 0; - } - toucher.lastpushtime = time; - if(!pushdeltatime) - { - return; - } - - if(this.spawnflags & IMPULSE_DIRECTIONAL_SPEEDTARGET) - { - float addspeed = str - toucher.velocity * normalize(targ.origin - this.origin); - if (addspeed > 0) - { - float accelspeed = min(IMPULSE_DIRECTIONAL_MAX_ACCEL_FACTOR * pushdeltatime * str, addspeed); - toucher.velocity += accelspeed * normalize(targ.origin - this.origin); - } - } - else - toucher.velocity = toucher.velocity + normalize(targ.origin - this.origin) * str * pushdeltatime; - - UNSET_ONGROUND(toucher); - -#ifdef SVQC - UpdateCSQCProjectile(toucher); -#endif -} - -// Directionless (accelerator/decelerator) mode -void trigger_impulse_touch_accel(entity this, entity toucher) -{ - float pushdeltatime; - - if (this.active != ACTIVE_ACTIVE) - return; - - if (!isPushable(toucher)) - return; - - EXACTTRIGGER_TOUCH(this, toucher); - - pushdeltatime = time - toucher.lastpushtime; - if (pushdeltatime > IMPULSE_MAX_PUSHDELTATIME) - { - pushdeltatime = 0; - } - toucher.lastpushtime = time; - if(!pushdeltatime) - { - return; - } - - // div0: ticrate independent, 1 = identity (not 20) - toucher.velocity = toucher.velocity * (this.strength ** pushdeltatime); - -#ifdef SVQC - UpdateCSQCProjectile(toucher); -#endif -} - -// Spherical (gravity/repulsor) mode -void trigger_impulse_touch_radial(entity this, entity toucher) -{ - float pushdeltatime; - float str; - - if (this.active != ACTIVE_ACTIVE) - return; - - if (!isPushable(toucher)) - return; - - EXACTTRIGGER_TOUCH(this, toucher); - - pushdeltatime = time - toucher.lastpushtime; - if (pushdeltatime > IMPULSE_MAX_PUSHDELTATIME) - { - pushdeltatime = 0; - } - toucher.lastpushtime = time; - if(!pushdeltatime) - { - return; - } - - setsize(this, '-1 -1 -1' * this.radius,'1 1 1' * this.radius); - - str = min(this.radius, vlen(this.origin - toucher.origin)); - - if(this.falloff == FALLOFF_LINEAR) - str = (1 - str / this.radius) * this.strength; // 1 in the inside - else if(this.falloff == FALLOFF_LINEAR_INV) - str = (str / this.radius) * this.strength; // 0 in the inside - else - str = this.strength; - - toucher.velocity = toucher.velocity + normalize(toucher.origin - this.origin) * str * pushdeltatime; - -#ifdef SVQC - UpdateCSQCProjectile(toucher); -#endif -} - -REGISTER_NET_LINKED(ENT_CLIENT_TRIGGER_IMPULSE) - -/*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ? -Force field --------- KEYS -------- -target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed. - If not, this trigger acts like a damper/accelerator field. - -strength : This is how much force to add in the direction of .target each second - when .target is set. If not, this is how much to slow down/accelerate - something cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble) - -radius : If set, act as a spherical device rather then a linear one. - -falloff : 0 = none, 1 = liniar, 2 = inverted liniar - --------- NOTES -------- -Use a brush textured with common/origin in the trigger entity to determine the origin of the force -in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect). -*/ -#ifdef SVQC -bool trigger_impulse_send(entity this, entity to, int sf) -{ - WriteHeader(MSG_ENTITY, ENT_CLIENT_TRIGGER_IMPULSE); - - WriteByte(MSG_ENTITY, this.spawnflags); - WriteCoord(MSG_ENTITY, this.radius); - WriteCoord(MSG_ENTITY, this.strength); - WriteByte(MSG_ENTITY, this.falloff); - WriteByte(MSG_ENTITY, this.active); - - trigger_common_write(this, true); - - return true; -} - -void trigger_impulse_link(entity this) -{ - trigger_link(this, trigger_impulse_send); -} - -spawnfunc(trigger_impulse) -{ - this.active = ACTIVE_ACTIVE; - - trigger_init(this); - - if(this.radius) - { - if(!this.strength) - { - this.strength = IMPULSE_DEFAULT_RADIAL_STRENGTH * autocvar_g_triggerimpulse_radial_multiplier; - } - setorigin(this, this.origin); - setsize(this, '-1 -1 -1' * this.radius,'1 1 1' * this.radius); - settouch(this, trigger_impulse_touch_radial); - } - else - { - if(this.target) - { - if(!this.strength) - { - this.strength = IMPULSE_DEFAULT_DIRECTIONAL_STRENGTH * autocvar_g_triggerimpulse_directional_multiplier; - } - settouch(this, trigger_impulse_touch_directional); - } - else - { - if(!this.strength) - { - this.strength = IMPULSE_DEFAULT_ACCEL_STRENGTH; - } - this.strength = (this.strength ** autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier; - settouch(this, trigger_impulse_touch_accel); - } - } - - trigger_impulse_link(this); -} -#elif defined(CSQC) -NET_HANDLE(ENT_CLIENT_TRIGGER_IMPULSE, bool isnew) -{ - this.spawnflags = ReadByte(); - this.radius = ReadCoord(); - this.strength = ReadCoord(); - this.falloff = ReadByte(); - this.active = ReadByte(); - - trigger_common_read(this, true); - return = true; - - this.classname = "trigger_impulse"; - this.solid = SOLID_TRIGGER; - this.entremove = trigger_remove_generic; - this.move_time = time; - - if (this.radius) - { - settouch(this, trigger_impulse_touch_radial); - } - else if (this.target) - { - settouch(this, trigger_impulse_touch_directional); - } - else - { - settouch(this, trigger_impulse_touch_accel); - } -} -#endif diff --git a/qcsrc/common/triggers/trigger/impulse.qh b/qcsrc/common/triggers/trigger/impulse.qh deleted file mode 100644 index e86de4a49..000000000 --- a/qcsrc/common/triggers/trigger/impulse.qh +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -// tZorks trigger impulse / gravity -.float radius; -.int falloff; -.float strength; -.float lastpushtime; - -const int FALLOFF_NO = 0; -const int FALLOFF_LINEAR = 1; -const int FALLOFF_LINEAR_INV = 2; - -const int IMPULSE_DIRECTIONAL_SPEEDTARGET = BIT(6); - -const float IMPULSE_DEFAULT_RADIAL_STRENGTH = 2000; -const float IMPULSE_DEFAULT_DIRECTIONAL_STRENGTH = 950; -const float IMPULSE_DEFAULT_ACCEL_STRENGTH = 0.9; - -const float IMPULSE_MAX_PUSHDELTATIME = 0.15; - -const float IMPULSE_DIRECTIONAL_MAX_ACCEL_FACTOR = 8; diff --git a/qcsrc/common/triggers/trigger/include.qc b/qcsrc/common/triggers/trigger/include.qc deleted file mode 100644 index 1c762fc35..000000000 --- a/qcsrc/common/triggers/trigger/include.qc +++ /dev/null @@ -1,25 +0,0 @@ -#include "include.qh" - -#include "counter.qc" -#include "delay.qc" -#include "disablerelay.qc" -#include "flipflop.qc" -#include "gamestart.qc" -#include "gravity.qc" -#include "heal.qc" -#include "hurt.qc" -#include "impulse.qc" -#include "jumppads.qc" -#include "keylock.qc" -#include "magicear.qc" -#include "monoflop.qc" -#include "multi.qc" -#include "multivibrator.qc" -#include "relay.qc" -#include "relay_activators.qc" -#include "relay_if.qc" -#include "relay_teamcheck.qc" -#include "secret.qc" -#include "swamp.qc" -#include "teleport.qc" -#include "viewloc.qc" diff --git a/qcsrc/common/triggers/trigger/include.qh b/qcsrc/common/triggers/trigger/include.qh deleted file mode 100644 index 8aa6b2b17..000000000 --- a/qcsrc/common/triggers/trigger/include.qh +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "multi.qh" -#include "jumppads.qh" -#include "secret.qh" -#include "swamp.qh" -#include "keylock.qh" -#include "impulse.qh" -#include "viewloc.qh" diff --git a/qcsrc/common/triggers/trigger/jumppads.qc b/qcsrc/common/triggers/trigger/jumppads.qc deleted file mode 100644 index 5ffdf2d10..000000000 --- a/qcsrc/common/triggers/trigger/jumppads.qc +++ /dev/null @@ -1,676 +0,0 @@ -#include "jumppads.qh" -// TODO: split target_push and put it in the target folder -#ifdef SVQC -#include - -void trigger_push_use(entity this, entity actor, entity trigger) -{ - if(teamplay) - { - this.team = actor.team; - this.SendFlags |= SF_TRIGGER_UPDATE; - } -} -#endif - -REGISTER_NET_LINKED(ENT_CLIENT_TRIGGER_PUSH) -REGISTER_NET_LINKED(ENT_CLIENT_TARGET_PUSH) - -/* - trigger_push_calculatevelocity - - Arguments: - org - origin of the object which is to be pushed - tgt - target entity (can be either a point or a model entity; if it is - the latter, its midpoint is used) - ht - jump height, measured from the higher one of org and tgt's midpoint - pushed_entity - object that is to be pushed - - Returns: velocity for the jump - */ -vector trigger_push_calculatevelocity(vector org, entity tgt, float ht, entity pushed_entity) -{ - float grav, sdist, zdist, vs, vz, jumpheight; - vector sdir, torg; - - torg = tgt.origin + (tgt.mins + tgt.maxs) * 0.5; - - grav = PHYS_GRAVITY(NULL); - if(pushed_entity && PHYS_ENTGRAVITY(pushed_entity)) - grav *= PHYS_ENTGRAVITY(pushed_entity); - - zdist = torg.z - org.z; - sdist = vlen(torg - org - zdist * '0 0 1'); - sdir = normalize(torg - org - zdist * '0 0 1'); - - // how high do we need to push the player? - jumpheight = fabs(ht); - if(zdist > 0) - jumpheight = jumpheight + zdist; - - /* - STOP. - - You will not understand the following equations anyway... - But here is what I did to get them. - - I used the functions - - s(t) = t * vs - z(t) = t * vz - 1/2 grav t^2 - - and solved for: - - s(ti) = sdist - z(ti) = zdist - max(z, ti) = jumpheight - - From these three equations, you will find the three parameters vs, vz - and ti. - */ - - // push him so high... - vz = sqrt(fabs(2 * grav * jumpheight)); // NOTE: sqrt(positive)! - - // we start with downwards velocity only if it's a downjump and the jump apex should be outside the jump! - if(ht < 0) - if(zdist < 0) - vz = -vz; - - vector solution; - solution = solve_quadratic(0.5 * grav, -vz, zdist); // equation "z(ti) = zdist" - // ALWAYS solvable because jumpheight >= zdist - if(!solution.z) - solution_y = solution.x; // just in case it is not solvable due to roundoff errors, assume two equal solutions at their center (this is mainly for the usual case with ht == 0) - if(zdist == 0) - solution_x = solution.y; // solution_x is 0 in this case, so don't use it, but rather use solution_y (which will be sqrt(0.5 * jumpheight / grav), actually) - - float flighttime; - if(zdist < 0) - { - // down-jump - if(ht < 0) - { - // almost straight line type - // jump apex is before the jump - // we must take the larger one - flighttime = solution.y; - } - else - { - // regular jump - // jump apex is during the jump - // we must take the larger one too - flighttime = solution.y; - } - } - else - { - // up-jump - if(ht < 0) - { - // almost straight line type - // jump apex is after the jump - // we must take the smaller one - flighttime = solution.x; - } - else - { - // regular jump - // jump apex is during the jump - // we must take the larger one - flighttime = solution.y; - } - } - vs = sdist / flighttime; - - // finally calculate the velocity - return sdir * vs + '0 0 1' * vz; -} - -bool jumppad_push(entity this, entity targ) -{ - if (!isPushable(targ)) - return false; - - if(this.enemy) - { - targ.velocity = trigger_push_calculatevelocity(targ.origin, this.enemy, this.height, targ); - } - else if(this.target && this.target != "") - { - entity e; - RandomSelection_Init(); - for(e = NULL; (e = find(e, targetname, this.target)); ) - { - if(e.cnt) - RandomSelection_AddEnt(e, e.cnt, 1); - else - RandomSelection_AddEnt(e, 1, 1); - } - targ.velocity = trigger_push_calculatevelocity(targ.origin, RandomSelection_chosen_ent, this.height, targ); - } - else - { - targ.velocity = this.movedir; - } - - UNSET_ONGROUND(targ); - -#ifdef CSQC - if (targ.flags & FL_PROJECTILE) - { - targ.angles = vectoangles (targ.velocity); - switch(targ.move_movetype) - { - case MOVETYPE_FLY: - set_movetype(targ, MOVETYPE_TOSS); - targ.gravity = 1; - break; - case MOVETYPE_BOUNCEMISSILE: - set_movetype(targ, MOVETYPE_BOUNCE); - targ.gravity = 1; - break; - } - } -#endif - -#ifdef SVQC - if (IS_PLAYER(targ)) - { - // reset tracking of oldvelocity for impact damage (sudden velocity changes) - targ.oldvelocity = targ.velocity; - - if(this.pushltime < time) // prevent "snorring" sound when a player hits the jumppad more than once - { - // flash when activated - Send_Effect(EFFECT_JUMPPAD, targ.origin, targ.velocity, 1); - _sound (targ, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM); - this.pushltime = time + 0.2; - } - if(IS_REAL_CLIENT(targ) || IS_BOT_CLIENT(targ)) - { - bool found = false; - for(int i = 0; i < targ.jumppadcount && i < NUM_JUMPPADSUSED; ++i) - if(targ.(jumppadsused[i]) == this) - found = true; - if(!found) - { - targ.(jumppadsused[targ.jumppadcount % NUM_JUMPPADSUSED]) = this; - targ.jumppadcount = targ.jumppadcount + 1; - } - - if(IS_REAL_CLIENT(targ)) - { - if(this.message) - centerprint(targ, this.message); - } - else - { - targ.lastteleporttime = time; - targ.lastteleport_origin = targ.origin; - } - - if (!IS_DEAD(targ)) - animdecide_setaction(targ, ANIMACTION_JUMP, true); - } - else - targ.jumppadcount = 1; - - // reset tracking of who pushed you into a hazard (for kill credit) - targ.pushltime = 0; - targ.istypefrag = 0; - } - - if(this.enemy.target) - SUB_UseTargets(this.enemy, targ, this); - - if (targ.flags & FL_PROJECTILE) - { - targ.angles = vectoangles (targ.velocity); - targ.com_phys_gravity_factor = 1; - switch(targ.move_movetype) - { - case MOVETYPE_FLY: - set_movetype(targ, MOVETYPE_TOSS); - targ.gravity = 1; - break; - case MOVETYPE_BOUNCEMISSILE: - set_movetype(targ, MOVETYPE_BOUNCE); - targ.gravity = 1; - break; - } - UpdateCSQCProjectile(targ); - } -#endif - - return true; -} - -void trigger_push_touch(entity this, entity toucher) -{ - if (this.active == ACTIVE_NOT) - return; - - if(this.team) - if(((this.spawnflags & INVERT_TEAMS) == 0) == (DIFF_TEAM(this, toucher))) - return; - - EXACTTRIGGER_TOUCH(this, toucher); - - noref bool success = jumppad_push(this, toucher); - -#ifdef SVQC - if (success && (this.spawnflags & PUSH_ONCE)) - { - settouch(this, func_null); - setthink(this, SUB_Remove); - this.nextthink = time; - } -#endif -} - -#ifdef SVQC -void trigger_push_link(entity this); -void trigger_push_updatelink(entity this); -bool trigger_push_testorigin(entity tracetest_ent, entity targ, entity jp, vector org) -{ - setorigin(tracetest_ent, org); - tracetoss(tracetest_ent, tracetest_ent); - if(trace_startsolid) - return false; - - if (!jp.height) - { - // since tracetoss starting from jumppad's origin often fails when target - // is very close to real destination, start it directly from target's - // origin instead - vector ofs = '0 0 0'; - if (vdist(vec2(tracetest_ent.velocity), <, autocvar_sv_maxspeed)) - ofs = stepheightvec; - - tracetest_ent.velocity.z = 0; - setorigin(tracetest_ent, targ.origin + ofs); - tracetoss(tracetest_ent, tracetest_ent); - if (trace_startsolid && ofs.z) - { - setorigin(tracetest_ent, targ.origin + ofs / 2); - tracetoss(tracetest_ent, tracetest_ent); - if (trace_startsolid && ofs.z) - { - setorigin(tracetest_ent, targ.origin); - tracetoss(tracetest_ent, tracetest_ent); - if (trace_startsolid) - return false; - } - } - } - tracebox(trace_endpos, tracetest_ent.mins, tracetest_ent.maxs, trace_endpos - eZ * 1500, true, tracetest_ent); - return true; -} - -bool trigger_push_testorigin_for_item(entity tracetest_ent, entity item, vector org) -{ - setorigin(tracetest_ent, org); - tracetoss(tracetest_ent, tracetest_ent); - - if(trace_startsolid) - return false; - if (trace_ent == item) - return true; - - tracebox(trace_endpos, tracetest_ent.mins, tracetest_ent.maxs, trace_endpos - eZ * 1500, true, tracetest_ent); - - if (trace_ent == item) - return true; - - return false; -} -#endif - -/// if (item != NULL) returns true if the item can be reached by using this jumppad, false otherwise -/// if (item == NULL) tests jumppad's trajectory and eventually spawns waypoints for it (return value doesn't matter) -bool trigger_push_test(entity this, entity item) -{ - // first calculate a typical start point for the jump - vector org = (this.absmin + this.absmax) * 0.5; - org.z = this.absmax.z - PL_MIN_CONST.z - 7; - - if (this.target) - { - int n = 0; -#ifdef SVQC - vector vel = '0 0 0'; -#endif - for(entity t = NULL; (t = find(t, targetname, this.target)); ) - { - ++n; -#ifdef SVQC - if(t.move_movetype != MOVETYPE_NONE) - continue; - - entity e = spawn(); - setsize(e, PL_MIN_CONST, PL_MAX_CONST); - e.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; - e.velocity = trigger_push_calculatevelocity(org, t, this.height, e); - - vel = e.velocity; - vector best_target = '0 0 0'; - vector best_org = '0 0 0'; - vector best_vel = '0 0 0'; - bool valid_best_target = false; - if (item) - { - if (!trigger_push_testorigin_for_item(e, item, org)) - { - delete(e); - return false; - } - } - else - { - if (trigger_push_testorigin(e, t, this, org)) - { - best_target = trace_endpos; - best_org = org; - best_vel = e.velocity; - valid_best_target = true; - } - } - - vector new_org; - vector dist = t.origin - org; - if (dist.x || dist.y) // if not perfectly vertical - { - // test trajectory with different starting points, sometimes the trajectory - // starting from the jumppad origin can't reach the real destination - // and destination waypoint ends up near the jumppad itself - vector flatdir = normalize(dist - eZ * dist.z); - vector ofs = flatdir * 0.5 * min(fabs(this.absmax.x - this.absmin.x), fabs(this.absmax.y - this.absmin.y)); - new_org = org + ofs; - - LABEL(new_test) - e.velocity = trigger_push_calculatevelocity(new_org, t, this.height, e); - if (item) - { - if (!trigger_push_testorigin_for_item(e, item, new_org)) - { - delete(e); - return false; - } - } - else - { - vel = e.velocity; - if (vdist(vec2(e.velocity), <, autocvar_sv_maxspeed)) - e.velocity = autocvar_sv_maxspeed * flatdir; - if (trigger_push_testorigin(e, t, this, new_org) && (!valid_best_target || trace_endpos.z > best_target.z + 50)) - { - best_target = trace_endpos; - best_org = new_org; - best_vel = vel; - valid_best_target = true; - } - } - if (ofs && new_org != org - ofs) - { - new_org = org - ofs; - goto new_test; - } - } - - if (item) - { - delete(e); - return true; - } - - if (valid_best_target) - { - if (!(boxesoverlap(this.absmin, this.absmax + eZ * 50, best_target + PL_MIN_CONST, best_target + PL_MAX_CONST))) - { - float velxy = vlen(vec2(best_vel)); - float cost = vlen(vec2(t.origin - best_org)) / velxy; - if(velxy < autocvar_sv_maxspeed) - velxy = autocvar_sv_maxspeed; - cost += vlen(vec2(best_target - t.origin)) / velxy; - waypoint_spawnforteleporter(this, best_target, cost, e); - } - } - delete(e); -#endif - } - - if(item) - return false; - - if(!n) - { - // no dest! -#ifdef SVQC - objerror (this, "Jumppad with nonexistant target"); -#endif - return false; - } - else if(n == 1) - { - // exactly one dest - bots love that - this.enemy = find(NULL, targetname, this.target); - } - else - { - // have to use random selection every single time - this.enemy = NULL; - } - } -#ifdef SVQC - else - { - entity e = spawn(); - setsize(e, PL_MIN_CONST, PL_MAX_CONST); - e.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; - setorigin(e, org); - e.velocity = this.movedir; - tracetoss(e, e); - if (item) - { - bool r = (trace_ent == item); - delete(e); - return r; - } - if (!(boxesoverlap(this.absmin, this.absmax + eZ * 50, trace_endpos + PL_MIN_CONST, trace_endpos + PL_MAX_CONST))) - waypoint_spawnforteleporter(this, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity), e); - delete(e); - } - - defer(this, 0.1, trigger_push_updatelink); -#endif - return true; -} - -void trigger_push_findtarget(entity this) -{ - trigger_push_test(this, NULL); -} - -#ifdef SVQC -float trigger_push_send(entity this, entity to, float sf) -{ - WriteHeader(MSG_ENTITY, ENT_CLIENT_TRIGGER_PUSH); - - WriteByte(MSG_ENTITY, this.team); - WriteInt24_t(MSG_ENTITY, this.spawnflags); - WriteByte(MSG_ENTITY, this.active); - WriteCoord(MSG_ENTITY, this.height); - - WriteVector(MSG_ENTITY, this.movedir); - - trigger_common_write(this, true); - - return true; -} - -void trigger_push_updatelink(entity this) -{ - this.SendFlags |= SF_TRIGGER_INIT; -} - -void trigger_push_link(entity this) -{ - trigger_link(this, trigger_push_send); -} - -/* - * ENTITY PARAMETERS: - * - * target: target of jump - * height: the absolute value is the height of the highest point of the jump - * trajectory above the higher one of the player and the target. - * the sign indicates whether the highest point is INSIDE (positive) - * or OUTSIDE (negative) of the jump trajectory. General rule: use - * positive values for targets mounted on the floor, and use negative - * values to target a point on the ceiling. - * movedir: if target is not set, this * speed * 10 is the velocity to be reached. - */ -spawnfunc(trigger_push) -{ - SetMovedir(this); - - trigger_init(this); - - this.active = ACTIVE_ACTIVE; - this.use = trigger_push_use; - settouch(this, trigger_push_touch); - - // normal push setup - if (!this.speed) - this.speed = 1000; - this.movedir = this.movedir * this.speed * 10; - - if (!this.noise) - this.noise = "misc/jumppad.wav"; - precache_sound (this.noise); - - trigger_push_link(this); // link it now - - IL_PUSH(g_jumppads, this); - - // this must be called to spawn the teleport waypoints for bots - InitializeEntity(this, trigger_push_findtarget, INITPRIO_FINDTARGET); -} - - -bool target_push_send(entity this, entity to, float sf) -{ - WriteHeader(MSG_ENTITY, ENT_CLIENT_TARGET_PUSH); - - WriteByte(MSG_ENTITY, this.cnt); - WriteString(MSG_ENTITY, this.targetname); - WriteVector(MSG_ENTITY, this.origin); - - WriteAngle(MSG_ENTITY, this.angles_x); - WriteAngle(MSG_ENTITY, this.angles_y); - WriteAngle(MSG_ENTITY, this.angles_z); - - return true; -} - -void target_push_use(entity this, entity actor, entity trigger) -{ - if(trigger.classname == "trigger_push" || trigger == this) - return; // WTF, why is this a thing - - jumppad_push(this, actor); -} - -void target_push_link(entity this) -{ - BITSET_ASSIGN(this.effects, EF_NODEPTHTEST); - Net_LinkEntity(this, false, 0, target_push_send); - //this.SendFlags |= 1; // update -} - -void target_push_init(entity this) -{ - this.mangle = this.angles; - setorigin(this, this.origin); - target_push_link(this); -} - -void target_push_init2(entity this) -{ - if(this.target && this.target != "") // we have an old style pusher! - { - InitializeEntity(this, trigger_push_findtarget, INITPRIO_FINDTARGET); - this.use = target_push_use; - } - - target_push_init(this); // normal push target behaviour can be combined with a legacy pusher? -} - -spawnfunc(target_push) -{ - target_push_init2(this); -} - -spawnfunc(info_notnull) -{ - target_push_init(this); -} -spawnfunc(target_position) -{ - target_push_init(this); -} - -#elif defined(CSQC) - -NET_HANDLE(ENT_CLIENT_TRIGGER_PUSH, bool isnew) -{ - this.classname = "jumppad"; - int mytm = ReadByte(); - if(mytm) - { - this.team = mytm - 1; - } - this.spawnflags = ReadInt24_t(); - this.active = ReadByte(); - this.height = ReadCoord(); - - this.movedir = ReadVector(); - - trigger_common_read(this, true); - - this.entremove = trigger_remove_generic; - this.solid = SOLID_TRIGGER; - settouch(this, trigger_push_touch); - this.move_time = time; - defer(this, 0.25, trigger_push_findtarget); - - return true; -} - -void target_push_remove(entity this) -{ - // strfree(this.classname); - strfree(this.targetname); -} - -NET_HANDLE(ENT_CLIENT_TARGET_PUSH, bool isnew) -{ - this.classname = "push_target"; - this.cnt = ReadByte(); - this.targetname = strzone(ReadString()); - this.origin = ReadVector(); - - this.angles_x = ReadAngle(); - this.angles_y = ReadAngle(); - this.angles_z = ReadAngle(); - - return = true; - - setorigin(this, this.origin); - - this.drawmask = MASK_NORMAL; - this.entremove = target_push_remove; -} -#endif diff --git a/qcsrc/common/triggers/trigger/jumppads.qh b/qcsrc/common/triggers/trigger/jumppads.qh deleted file mode 100644 index cd6adec31..000000000 --- a/qcsrc/common/triggers/trigger/jumppads.qh +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - - -const int PUSH_ONCE = BIT(0); // legacy, deactivate with relay instead -const int PUSH_SILENT = BIT(1); // not used? - -IntrusiveList g_jumppads; -STATIC_INIT(g_jumppads) { g_jumppads = IL_NEW(); } - -.float pushltime; -.float istypefrag; -.float height; - -const int NUM_JUMPPADSUSED = 3; -.float jumppadcount; -.entity jumppadsused[NUM_JUMPPADSUSED]; - -#ifdef SVQC -void SUB_UseTargets(entity this, entity actor, entity trigger); -void trigger_push_use(entity this, entity actor, entity trigger); -bool trigger_push_testorigin(entity tracetest_ent, entity targ, entity jp, vector org); -bool trigger_push_testorigin_for_item(entity tracetest_ent, entity item, vector org); -#endif - -/* - trigger_push_calculatevelocity - - Arguments: - org - origin of the object which is to be pushed - tgt - target entity (can be either a point or a model entity; if it is - the latter, its midpoint is used) - ht - jump height, measured from the higher one of org and tgt's midpoint - pushed_entity - object that is to be pushed - - Returns: velocity for the jump - */ -vector trigger_push_calculatevelocity(vector org, entity tgt, float ht, entity pushed_entity); - -void trigger_push_touch(entity this, entity toucher); - -.vector dest; -bool trigger_push_test(entity this, entity item); -void trigger_push_findtarget(entity this); - -/* - * ENTITY PARAMETERS: - * - * target: target of jump - * height: the absolute value is the height of the highest point of the jump - * trajectory above the higher one of the player and the target. - * the sign indicates whether the highest point is INSIDE (positive) - * or OUTSIDE (negative) of the jump trajectory. General rule: use - * positive values for targets mounted on the floor, and use negative - * values to target a point on the ceiling. - * movedir: if target is not set, this * speed * 10 is the velocity to be reached. - */ -#ifdef SVQC -spawnfunc(trigger_push); - -spawnfunc(target_push); -spawnfunc(info_notnull); -spawnfunc(target_position); -#endif diff --git a/qcsrc/common/triggers/trigger/keylock.qc b/qcsrc/common/triggers/trigger/keylock.qc deleted file mode 100644 index 67db14421..000000000 --- a/qcsrc/common/triggers/trigger/keylock.qc +++ /dev/null @@ -1,187 +0,0 @@ -#include "keylock.qh" -/** - * trigger given targets - */ -void trigger_keylock_trigger(entity this, entity actor, string s) -{ - for(entity t = NULL; (t = find(t, targetname, s)); ) - if(t.use) - t.use(t, actor, this); -} - -/** - * kill killtarget of trigger keylock. - */ -void trigger_keylock_kill(string s) -{ - entity t; - for(t = NULL; (t = find(t, targetname, s)); ) - delete(t); -} - -void trigger_keylock_touch(entity this, entity toucher) -{ - bool key_used = false; - bool started_delay = false; - - // only player may trigger the lock - if(!IS_PLAYER(toucher)) - return; - - // check silver key - if(this.itemkeys) - key_used = item_keys_usekey(this, toucher); - - if(this.itemkeys) - { -#ifdef SVQC - // at least one of the keys is missing - if(key_used) - { - // one or more keys were given, but others are still missing! - play2(toucher, this.noise1); - Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(this.itemkeys)); - toucher.key_door_messagetime = time + 2; - } - else if(toucher.key_door_messagetime <= time) - { - // no keys were given - play2(toucher, this.noise2); - Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(this.itemkeys)); - toucher.key_door_messagetime = time + 2; - } -#endif - - // trigger target2 - if(this.delay <= time || started_delay == true) - if(this.target2) - { - trigger_keylock_trigger(this, toucher, this.target2); - started_delay = true; - this.delay = time + this.wait; - } - } - else - { -#ifdef SVQC - // all keys were given! - play2(toucher, this.noise); - centerprint(toucher, this.message); -#endif - - if(this.target) - trigger_keylock_trigger(this, toucher, this.target); - - if(this.killtarget) - trigger_keylock_kill(this.killtarget); - - delete(this); - } - -} - -REGISTER_NET_LINKED(ENT_CLIENT_KEYLOCK) - -#ifdef SVQC -bool trigger_keylock_send(entity this, entity to, int sf) -{ - WriteHeader(MSG_ENTITY, ENT_CLIENT_KEYLOCK); - - WriteInt24_t(MSG_ENTITY, this.itemkeys); - WriteByte(MSG_ENTITY, this.height); - - trigger_common_write(this, true); - - return true; -} - -void trigger_keylock_link(entity this) -{ - // uncomment to network keylocks - //Net_LinkEntity(this, false, 0, trigger_keylock_send); -} - -/*QUAKED trigger_keylock (.0 .5 .8) ? -Keylock trigger. Must target other entities. -This trigger will trigger target entities when all required keys are provided. --------- KEYS -------- -itemkeys: A bit field with key IDs that are needed to open this lock. -sounds: 1 to play misc/secret.wav, 2 to play misc/talk.wav, 3 to play misc/trigger1.wav (3 is default) -target: trigger all entities with this targetname when triggered and all keys have been given to it, then remove this trigger -target2: trigger all entities with this targetname when triggered without giving it all the required keys. -killtarget: remove all entities with this targetname when triggered with all the needed keys. -message: print this message to the player who activated the trigger when all needed keys have been given. -message2: print this message to the player who activated the trigger when not all of the needed keys have been given. -noise: sound to play when lock gets unlocked (default: see sounds) -noise1: sound to play when only some of the needed key were used but not all (default: misc/decreasevalue.wav) -noise2: sound to play when a key is missing (default: misc/talk.wav) -wait: prevent triggering again for this amount of time (default: 5) - applies to target2, target3, target4. ----------NOTES---------- -If spawned without any key specified in itemkeys, this trigger will display an error and remove itself. -message2 and noise2 will be resent to the player every 2 seconds while he is in the trigger zone. -*/ -spawnfunc(trigger_keylock) -{ - if(!this.itemkeys) { delete(this); return; } - - // set unlocked message - if(this.message == "") - this.message = "Unlocked!"; - - // set default unlock noise - if(this.noise == "") - { - if(this.sounds == 1) - this.noise = "misc/secret.wav"; - else if(this.sounds == 2) - this.noise = strzone(SND(TALK)); - else //if (this.sounds == 3) { - this.noise = "misc/trigger1.wav"; - } - - // set default use key sound - if(this.noise1 == "") - this.noise1 = "misc/decreasevalue.wav"; - - // set closed sourd - if(this.noise2 == "") - this.noise2 = SND(TALK); - - // delay between triggering message2 and trigger2 - if(!this.wait) { this.wait = 5; } - - // precache sounds - precache_sound(this.noise); - precache_sound(this.noise1); - precache_sound(this.noise2); - - EXACTTRIGGER_INIT; - - settouch(this, trigger_keylock_touch); - - trigger_keylock_link(this); -} -#elif defined(CSQC) -void keylock_remove(entity this) -{ - strfree(this.target); - strfree(this.target2); - strfree(this.target3); - strfree(this.target4); - strfree(this.killtarget); - strfree(this.targetname); -} - -NET_HANDLE(ENT_CLIENT_KEYLOCK, bool isnew) -{ - this.itemkeys = ReadInt24_t(); - this.height = ReadByte(); - - trigger_common_read(this, true); - - return = true; - - this.classname = "trigger_keylock"; - this.entremove = keylock_remove; -} -#endif diff --git a/qcsrc/common/triggers/trigger/keylock.qh b/qcsrc/common/triggers/trigger/keylock.qh deleted file mode 100644 index 904c3fa3d..000000000 --- a/qcsrc/common/triggers/trigger/keylock.qh +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#ifdef CSQC -bool item_keys_usekey(entity l, entity p) -{ - int valid = (l.itemkeys & p.itemkeys); // TODO: itemkeys isn't networked or anything! - l.itemkeys &= ~valid; // only some of the needed keys were given - return valid != 0; -} -#endif diff --git a/qcsrc/common/triggers/trigger/magicear.qc b/qcsrc/common/triggers/trigger/magicear.qc deleted file mode 100644 index 16118cb9d..000000000 --- a/qcsrc/common/triggers/trigger/magicear.qc +++ /dev/null @@ -1,200 +0,0 @@ -#include "magicear.qh" -#ifdef SVQC -float magicear_matched; -float W_Tuba_HasPlayed(entity pl, .entity weaponentity, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo); -string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin) -{ - float domatch, dotrigger, matchstart, l; - string s, msg; - string savemessage; - - magicear_matched = false; - - dotrigger = ((IS_PLAYER(source)) && (!IS_DEAD(source)) && ((ear.radius == 0) || (vdist(source.origin - ear.origin, <=, ear.radius)))); - domatch = ((ear.spawnflags & MAGICEAR_REPLACE_OUTSIDE) || dotrigger); - - if (!domatch) - return msgin; - - if (!msgin) - { - // we are in TUBA mode! - if (!(ear.spawnflags & MAGICEAR_TUBA)) - return msgin; - - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - if(!W_Tuba_HasPlayed(source, weaponentity, ear.message, ear.movedir_x, !(ear.spawnflags & MAGICEAR_TUBA_EXACTPITCH), ear.movedir_y, ear.movedir_z)) - return msgin; - } - - magicear_matched = true; - - if(dotrigger) - { - savemessage = ear.message; - ear.message = string_null; - SUB_UseTargets(ear, source, NULL); - ear.message = savemessage; - } - - if(ear.netname != "") - return ear.netname; - - return msgin; - } - - if(ear.spawnflags & MAGICEAR_TUBA) // ENOTUBA - return msgin; - - if(privatesay) - { - if(ear.spawnflags & MAGICEAR_IGNORE_TELL) - return msgin; - } - else - { - if(!teamsay) - if(ear.spawnflags & MAGICEAR_IGNORE_SAY) - return msgin; - if(teamsay > 0) - if(ear.spawnflags & MAGICEAR_IGNORE_TEAMSAY) - return msgin; - if(teamsay < 0) - if(ear.spawnflags & MAGICEAR_IGNORE_INVALIDTELL) - return msgin; - } - - matchstart = -1; - l = strlen(ear.message); - - if(ear.spawnflags & MAGICEAR_NODECOLORIZE) - msg = msgin; - else - msg = strdecolorize(msgin); - - if(substring(ear.message, 0, 1) == "*") - { - if(substring(ear.message, -1, 1) == "*") - { - // two wildcards - // as we need multi-replacement here... - s = substring(ear.message, 1, -2); - l -= 2; - if(strstrofs(msg, s, 0) >= 0) - matchstart = -2; // we use strreplace on s - } - else - { - // match at start - s = substring(ear.message, 1, -1); - l -= 1; - if(substring(msg, -l, l) == s) - matchstart = strlen(msg) - l; - } - } - else - { - if(substring(ear.message, -1, 1) == "*") - { - // match at end - s = substring(ear.message, 0, -2); - l -= 1; - if(substring(msg, 0, l) == s) - matchstart = 0; - } - else - { - // full match - s = ear.message; - if(msg == ear.message) - matchstart = 0; - } - } - - if(matchstart == -1) // no match - return msgin; - - magicear_matched = true; - - if(dotrigger) - { - savemessage = ear.message; - ear.message = string_null; - SUB_UseTargets(ear, source, NULL); - ear.message = savemessage; - } - - if(ear.spawnflags & MAGICEAR_REPLACE_WHOLE_MESSAGE) - { - return ear.netname; - } - else if(ear.netname != "") - { - if(matchstart < 0) - return strreplace(s, ear.netname, msg); - else - return strcat( - substring(msg, 0, matchstart), - ear.netname, - substring(msg, matchstart + l, -1) - ); - } - else - return msgin; -} - -entity magicears; -string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin) -{ - entity ear; - string msgout; - for(ear = magicears; ear; ear = ear.enemy) - { - msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin); - if(!(ear.spawnflags & MAGICEAR_CONTINUE)) - if(magicear_matched) - return msgout; - msgin = msgout; - } - return msgin; -} - -spawnfunc(trigger_magicear) -{ - this.enemy = magicears; - magicears = this; - - // actually handled in "say" processing - // spawnflags: - // 1 = ignore say - // 2 = ignore teamsay - // 4 = ignore tell - // 8 = ignore tell to unknown player - // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set) - // 32 = perform the replacement even if outside the radius or dead - // 64 = continue replacing/triggering even if this one matched - // 128 = don't decolorize message before matching - // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...) - // 512 = tuba notes must be exact right pitch, no transposing - // message: either - // *pattern* - // or - // *pattern - // or - // pattern* - // or - // pattern - // netname: - // if set, replacement for the matched text - // radius: - // "hearing distance" - // target: - // what to trigger - // movedir: - // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter) - - this.movedir_x -= 1; // map to tuba instrument numbers -} -#endif diff --git a/qcsrc/common/triggers/trigger/magicear.qh b/qcsrc/common/triggers/trigger/magicear.qh deleted file mode 100644 index 4e705868c..000000000 --- a/qcsrc/common/triggers/trigger/magicear.qh +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - - -const int MAGICEAR_IGNORE_SAY = BIT(0); -const int MAGICEAR_IGNORE_TEAMSAY = BIT(1); -const int MAGICEAR_IGNORE_TELL = BIT(2); -const int MAGICEAR_IGNORE_INVALIDTELL = BIT(3); -const int MAGICEAR_REPLACE_WHOLE_MESSAGE = BIT(4); -const int MAGICEAR_REPLACE_OUTSIDE = BIT(5); -const int MAGICEAR_CONTINUE = BIT(6); -const int MAGICEAR_NODECOLORIZE = BIT(7); -const int MAGICEAR_TUBA = BIT(8); -const int MAGICEAR_TUBA_EXACTPITCH = BIT(9); diff --git a/qcsrc/common/triggers/trigger/monoflop.qc b/qcsrc/common/triggers/trigger/monoflop.qc deleted file mode 100644 index 0c960ba8a..000000000 --- a/qcsrc/common/triggers/trigger/monoflop.qc +++ /dev/null @@ -1,49 +0,0 @@ -#include "monoflop.qh" -#ifdef SVQC -/*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8) -"Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait" -*/ -void monoflop_use(entity this, entity actor, entity trigger) -{ - this.nextthink = time + this.wait; - this.enemy = actor; - if(this.state) - return; - this.state = 1; - SUB_UseTargets(this, actor, trigger); -} -void monoflop_fixed_use(entity this, entity actor, entity trigger) -{ - if(this.state) - return; - this.nextthink = time + this.wait; - this.state = 1; - this.enemy = actor; - SUB_UseTargets(this, actor, trigger); -} - -void monoflop_think(entity this) -{ - this.state = 0; - SUB_UseTargets(this, this.enemy, NULL); -} - -void monoflop_reset(entity this) -{ - this.state = 0; - this.nextthink = 0; -} - -spawnfunc(trigger_monoflop) -{ - if(!this.wait) - this.wait = 1; - if(this.spawnflags & MONOFLOP_FIXED) - this.use = monoflop_fixed_use; - else - this.use = monoflop_use; - setthink(this, monoflop_think); - this.state = 0; - this.reset = monoflop_reset; -} -#endif diff --git a/qcsrc/common/triggers/trigger/monoflop.qh b/qcsrc/common/triggers/trigger/monoflop.qh deleted file mode 100644 index c64dffdee..000000000 --- a/qcsrc/common/triggers/trigger/monoflop.qh +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - - -const int MONOFLOP_FIXED = BIT(0); diff --git a/qcsrc/common/triggers/trigger/multi.qc b/qcsrc/common/triggers/trigger/multi.qc deleted file mode 100644 index accfbe8ac..000000000 --- a/qcsrc/common/triggers/trigger/multi.qc +++ /dev/null @@ -1,224 +0,0 @@ -#include "multi.qh" -// NOTE: also contains trigger_once at bottom - -#ifdef SVQC -// the wait time has passed, so set back up for another activation -void multi_wait(entity this) -{ - if (this.max_health) - { - this.health = this.max_health; - this.takedamage = DAMAGE_YES; - this.solid = SOLID_BBOX; - } -} - - -// the trigger was just touched/killed/used -// this.enemy should be set to the activator so it can be held through a delay -// so wait for the delay time before firing -void multi_trigger(entity this) -{ - if (this.nextthink > time) - { - return; // allready been triggered - } - - if(this.spawnflags & ONLY_PLAYERS && !IS_PLAYER(this.enemy)) - { - return; // only players - } - - // TODO: restructure this so that trigger_secret is more independent - if (this.classname == "trigger_secret") - { - if (!IS_PLAYER(this.enemy)) - return; - found_secrets = found_secrets + 1; - WriteByte (MSG_ALL, SVC_FOUNDSECRET); - } - - if (this.noise) - { - _sound (this.enemy, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM); - } - - // don't trigger again until reset - this.takedamage = DAMAGE_NO; - - SUB_UseTargets(this, this.enemy, this.goalentity); - - if (this.wait > 0) - { - setthink(this, multi_wait); - this.nextthink = time + this.wait; - } - else if (this.wait == 0) - { - multi_wait(this); // waiting finished - } - else - { // we can't just delete(this) here, because this is a touch function - // called while C code is looping through area links... - settouch(this, func_null); - } -} - -void multi_use(entity this, entity actor, entity trigger) -{ - this.goalentity = trigger; - this.enemy = actor; - multi_trigger(this); -} - -void multi_touch(entity this, entity toucher) -{ - if(!(this.spawnflags & ALL_ENTITIES) && !toucher.iscreature) - { - return; - } - - if(this.team) - { - if(((this.spawnflags & INVERT_TEAMS) == 0) == (this.team != toucher.team)) - { - return; - } - } - - // if the trigger has an angles field, check player's facing direction - if (this.movedir != '0 0 0') - { - makevectors (toucher.angles); - if (v_forward * this.movedir < 0) - return; // not facing the right way - } - - // if the trigger has pressed keys, check that the player is pressing those keys - if(this.pressedkeys && IS_PLAYER(toucher)) // only for players - { - if(!(CS(toucher).pressedkeys & this.pressedkeys)) - { - return; - } - } - - EXACTTRIGGER_TOUCH(this, toucher); - - this.enemy = toucher; - this.goalentity = toucher; - multi_trigger(this); -} - -void multi_eventdamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) -{ - if(!this.takedamage) - return; - if(this.spawnflags & NOSPLASH) - if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH)) - return; - if(this.team) - if(((this.spawnflags & INVERT_TEAMS) == 0) == (this.team != attacker.team)) - return; - this.health = this.health - damage; - if (this.health <= 0) - { - this.enemy = attacker; - this.goalentity = inflictor; - multi_trigger(this); - } -} - -void multi_reset(entity this) -{ - if ( !(this.spawnflags & SPAWNFLAG_NOTOUCH) ) - settouch(this, multi_touch); - if (this.max_health) - { - this.health = this.max_health; - this.takedamage = DAMAGE_YES; - this.solid = SOLID_BBOX; - } - setthink(this, func_null); - this.nextthink = 0; - this.team = this.team_saved; -} - -/*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch -Variable sized repeatable trigger. Must be targeted at one or more entities. If "health" is set, the trigger must be killed to activate each time. -If "delay" is set, the trigger waits some time after activating before firing. -"wait" : Seconds between triggerings. (.2 default) -If notouch is set, the trigger is only fired by other entities, not by touching. -NOTOUCH has been obsoleted by spawnfunc_trigger_relay! -sounds -1) secret -2) beep beep -3) large switch -4) -set "message" to text string -*/ -spawnfunc(trigger_multiple) -{ - this.reset = multi_reset; - if (this.sounds == 1) - this.noise = "misc/secret.wav"; - else if (this.sounds == 2) - this.noise = strzone(SND(TALK)); - else if (this.sounds == 3) - this.noise = "misc/trigger1.wav"; - - if(this.noise) - precache_sound(this.noise); - - if (!this.wait) - this.wait = 0.2; - else if(this.wait < -1) - this.wait = 0; - this.use = multi_use; - - EXACTTRIGGER_INIT; - - this.team_saved = this.team; - IL_PUSH(g_saved_team, this); - - if (this.health) - { - if (this.spawnflags & SPAWNFLAG_NOTOUCH) - objerror (this, "health and notouch don't make sense\n"); - this.canteamdamage = true; - this.max_health = this.health; - this.event_damage = multi_eventdamage; - this.takedamage = DAMAGE_YES; - this.solid = SOLID_BBOX; - setorigin(this, this.origin); // make sure it links into the world - } - else - { - if ( !(this.spawnflags & SPAWNFLAG_NOTOUCH) ) - { - settouch(this, multi_touch); - setorigin(this, this.origin); // make sure it links into the world - } - } -} - - -/*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch -Variable sized trigger. Triggers once, then removes itself. You must set the key "target" to the name of another object in the level that has a matching -"targetname". If "health" is set, the trigger must be killed to activate. -If notouch is set, the trigger is only fired by other entities, not by touching. -if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired. -if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0. -sounds -1) secret -2) beep beep -3) large switch -4) -set "message" to text string -*/ -spawnfunc(trigger_once) -{ - this.wait = -1; - spawnfunc_trigger_multiple(this); -} -#endif diff --git a/qcsrc/common/triggers/trigger/multi.qh b/qcsrc/common/triggers/trigger/multi.qh deleted file mode 100644 index 43358c274..000000000 --- a/qcsrc/common/triggers/trigger/multi.qh +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#ifdef SVQC -void multi_trigger(entity this); -void multi_reset(entity this); - -spawnfunc(trigger_once); -#endif diff --git a/qcsrc/common/triggers/trigger/multivibrator.qc b/qcsrc/common/triggers/trigger/multivibrator.qc deleted file mode 100644 index 932fda13c..000000000 --- a/qcsrc/common/triggers/trigger/multivibrator.qc +++ /dev/null @@ -1,78 +0,0 @@ -#include "multivibrator.qh" -#ifdef SVQC -void multivibrator_send(entity this) -{ - float newstate; - float cyclestart; - - cyclestart = floor((time + this.phase) / (this.wait + this.respawntime)) * (this.wait + this.respawntime) - this.phase; - - newstate = (time < cyclestart + this.wait); - - if(this.state != newstate) - SUB_UseTargets(this, this, NULL); - this.state = newstate; - - if(this.state) - this.nextthink = cyclestart + this.wait + 0.01; - else - this.nextthink = cyclestart + this.wait + this.respawntime + 0.01; -} - -void multivibrator_send_think(entity this) -{ - multivibrator_send(this); -} - -void multivibrator_toggle(entity this, entity actor, entity trigger) -{ - if(this.nextthink == 0) - { - multivibrator_send(this); - } - else - { - if(this.state) - { - SUB_UseTargets(this, actor, trigger); - this.state = 0; - } - this.nextthink = 0; - } -} - -void multivibrator_reset(entity this) -{ - if(!(this.spawnflags & START_ENABLED)) - this.nextthink = 0; // wait for a trigger event - else - this.nextthink = max(1, time); -} - -/*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED -"Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off. --------- KEYS -------- -target: trigger all entities with this targetname when it goes off -targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state -phase: offset of the timing -wait: "on" cycle time (default: 1) -respawntime: "off" cycle time (default: same as wait) --------- SPAWNFLAGS -------- -START_ENABLED: assume it is already turned on (when targeted) -*/ -spawnfunc(trigger_multivibrator) -{ - if(!this.wait) - this.wait = 1; - if(!this.respawntime) - this.respawntime = this.wait; - - this.state = 0; - this.use = multivibrator_toggle; - setthink(this, multivibrator_send_think); - this.nextthink = max(1, time); - - IFTARGETED - multivibrator_reset(this); -} -#endif diff --git a/qcsrc/common/triggers/trigger/multivibrator.qh b/qcsrc/common/triggers/trigger/multivibrator.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/trigger/multivibrator.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/trigger/relay.qc b/qcsrc/common/triggers/trigger/relay.qc deleted file mode 100644 index f99d364ae..000000000 --- a/qcsrc/common/triggers/trigger/relay.qc +++ /dev/null @@ -1,26 +0,0 @@ -#include "relay.qh" -#ifdef SVQC - -void relay_use(entity this, entity actor, entity trigger) -{ - if(this.active != ACTIVE_ACTIVE) - return; - - SUB_UseTargets(this, actor, trigger); -} - -/*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) -This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages. -*/ -spawnfunc(trigger_relay) -{ - this.active = ACTIVE_ACTIVE; - this.use = relay_use; - this.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully -} - -spawnfunc(target_relay) -{ - spawnfunc_trigger_relay(this); -} -#endif diff --git a/qcsrc/common/triggers/trigger/relay.qh b/qcsrc/common/triggers/trigger/relay.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/trigger/relay.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/trigger/relay_activators.qc b/qcsrc/common/triggers/trigger/relay_activators.qc deleted file mode 100644 index 18c2a40d0..000000000 --- a/qcsrc/common/triggers/trigger/relay_activators.qc +++ /dev/null @@ -1,34 +0,0 @@ -#include "relay_activators.qh" -#ifdef SVQC -void relay_activators_use(entity this, entity actor, entity trigger) -{ - for(entity trg = NULL; (trg = find(trg, targetname, this.target)); ) - { - if (trg.setactive) - trg.setactive(trg, this.cnt); - else - { - //bprint("Not using setactive\n"); - generic_setactive(trg, this.cnt); - } - } -} - -spawnfunc(relay_activate) -{ - this.cnt = ACTIVE_ACTIVE; - this.use = relay_activators_use; -} - -spawnfunc(relay_deactivate) -{ - this.cnt = ACTIVE_NOT; - this.use = relay_activators_use; -} - -spawnfunc(relay_activatetoggle) -{ - this.cnt = ACTIVE_TOGGLE; - this.use = relay_activators_use; -} -#endif diff --git a/qcsrc/common/triggers/trigger/relay_activators.qh b/qcsrc/common/triggers/trigger/relay_activators.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/trigger/relay_activators.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/trigger/relay_if.qc b/qcsrc/common/triggers/trigger/relay_if.qc deleted file mode 100644 index 9adcd666e..000000000 --- a/qcsrc/common/triggers/trigger/relay_if.qc +++ /dev/null @@ -1,20 +0,0 @@ -#include "relay_if.qh" -#ifdef SVQC -void trigger_relay_if_use(entity this, entity actor, entity trigger) -{ - int n = this.count; - - // TODO make this generic AND faster than nextent()ing through all, if somehow possible - n = (cvar_string(this.netname) == cvar_string(this.message)); - if(this.spawnflags & RELAYIF_NEGATE) - n = !n; - - if(n) - SUB_UseTargets(this, actor, trigger); -} - -spawnfunc(trigger_relay_if) -{ - this.use = trigger_relay_if_use; -} -#endif diff --git a/qcsrc/common/triggers/trigger/relay_if.qh b/qcsrc/common/triggers/trigger/relay_if.qh deleted file mode 100644 index 6f37aa71d..000000000 --- a/qcsrc/common/triggers/trigger/relay_if.qh +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - - -const int RELAYIF_NEGATE = BIT(0); diff --git a/qcsrc/common/triggers/trigger/relay_teamcheck.qc b/qcsrc/common/triggers/trigger/relay_teamcheck.qc deleted file mode 100644 index bf03b1542..000000000 --- a/qcsrc/common/triggers/trigger/relay_teamcheck.qc +++ /dev/null @@ -1,37 +0,0 @@ -#include "relay_teamcheck.qh" -#ifdef SVQC -void trigger_relay_teamcheck_use(entity this, entity actor, entity trigger) -{ - if(actor.team) - { - if(this.spawnflags & RELAYTEAMCHECK_INVERT) - { - if(DIFF_TEAM(actor, this)) - SUB_UseTargets(this, actor, trigger); - } - else - { - if(SAME_TEAM(actor, this)) - SUB_UseTargets(this, actor, trigger); - } - } - else - { - if(this.spawnflags & RELAYTEAMCHECK_NOTEAM) - SUB_UseTargets(this, actor, trigger); - } -} - -void trigger_relay_teamcheck_reset(entity this) -{ - this.team = this.team_saved; -} - -spawnfunc(trigger_relay_teamcheck) -{ - this.team_saved = this.team; - IL_PUSH(g_saved_team, this); - this.use = trigger_relay_teamcheck_use; - this.reset = trigger_relay_teamcheck_reset; -} -#endif diff --git a/qcsrc/common/triggers/trigger/relay_teamcheck.qh b/qcsrc/common/triggers/trigger/relay_teamcheck.qh deleted file mode 100644 index 602d25356..000000000 --- a/qcsrc/common/triggers/trigger/relay_teamcheck.qh +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - - -const int RELAYTEAMCHECK_NOTEAM = BIT(0); -const int RELAYTEAMCHECK_INVERT = BIT(1); diff --git a/qcsrc/common/triggers/trigger/secret.qc b/qcsrc/common/triggers/trigger/secret.qc deleted file mode 100644 index 9377332e2..000000000 --- a/qcsrc/common/triggers/trigger/secret.qc +++ /dev/null @@ -1,90 +0,0 @@ -#include "secret.qh" -#if defined(CSQC) -#elif defined(MENUQC) -#elif defined(SVQC) - #include - #include -#endif - -#ifdef SVQC - -void secrets_setstatus(entity this) -{ - // TODO: use global stats! - STAT(SECRETS_TOTAL, this) = secrets_total; - STAT(SECRETS_FOUND, this) = secrets_found; -} - -/** - * A secret has been found (maybe :P) - */ -void trigger_secret_touch(entity this, entity toucher) -{ - // only a player can trigger this - if (!IS_PLAYER(toucher)) - return; - - // update secrets found counter - secrets_found += 1; - //print("Secret found: ", ftos(secret_counter.cnt), "/"); - //print(ftos(secret_counter.count), "\n"); - - // centerprint message (multi_touch() doesn't always call centerprint()) - centerprint(toucher, this.message); - this.message = ""; - - // handle normal trigger features - multi_touch(this, toucher); - // we can't just delete(this) here, because this is a touch function - // called while C code is looping through area links... - //delete(this); -} - -/*QUAKED trigger_secret (.5 .5 .5) ? -Variable sized secret trigger. Can be targeted at one or more entities. -Basically, it's a trigger_once (with restrictions, see notes) that additionally updates the number of secrets found. --------- KEYS -------- -sounds: 1 to play misc/secret.wav, 2 to play misc/talk.wav, 3 to play misc/trigger1.wav (default: 1) -noise: path to sound file, if you want to play something else -target: trigger all entities with this targetname when triggered -message: print this message to the player who activated the trigger instead of the standard 'You found a secret!' -killtarget: remove all entities with this targetname when triggered --------- NOTES -------- -You should create a common/trigger textured brush covering the entrance to a secret room/area. -Trigger secret can only be trigger by a player's touch and can not be a target itself. -*/ -spawnfunc(trigger_secret) -{ - // FIXME: should it be disabled in most modes? - - // update secrets count - secrets_total += 1; - - // add default message - if (this.message == "") - this.message = "You found a secret!"; - - // set default sound - if (this.noise == "") - if (!this.sounds) - this.sounds = 1; // misc/secret.wav - - // this entity can't be a target itself!!!! - this.targetname = ""; - - // you can't just shoot a room to find it, can you? - this.health = 0; - - // a secret can not be delayed - this.delay = 0; - - // convert this trigger to trigger_once - //this.classname = "trigger_once"; - spawnfunc_trigger_once(this); - - // take over the touch() function, so we can mark secret as found - settouch(this, trigger_secret_touch); - // ignore triggering; - this.use = func_null; -} -#endif diff --git a/qcsrc/common/triggers/trigger/secret.qh b/qcsrc/common/triggers/trigger/secret.qh deleted file mode 100644 index fcc55c395..000000000 --- a/qcsrc/common/triggers/trigger/secret.qh +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once -#ifdef SVQC - -/** - * Total number of secrets on the map. - */ -float secrets_total; - -/** - * Total numbe of secrets found on the map. - */ -float secrets_found; - - -/** - * update secrets status. - */ -void secrets_setstatus(entity this); -#endif diff --git a/qcsrc/common/triggers/trigger/swamp.qc b/qcsrc/common/triggers/trigger/swamp.qc deleted file mode 100644 index 058e41ca2..000000000 --- a/qcsrc/common/triggers/trigger/swamp.qc +++ /dev/null @@ -1,157 +0,0 @@ -#include "swamp.qh" -#if defined(CSQC) -#elif defined(MENUQC) -#elif defined(SVQC) - #include - #include - #include - #include -#endif - -/* -* t_swamp.c -* Adds spawnfunc_trigger_swamp and suppoart routines for xonotic 1.2.1+ -* Author tZork (Jakob MG) -* jakob@games43.se -* 2005 11 29 -*/ - -.float swamp_interval; //Hurt players in swamp with this interval -.float swamp_slowdown; //Players in swamp get slowd down by this mutch 0-1 is slowdown 1-~ is speedup (!?) -.entity swampslug; - -#ifdef SVQC -spawnfunc(trigger_swamp); -#endif -void swamp_touch(entity this, entity toucher); -void swampslug_think(entity this); - - -/* -* Uses a entity calld swampslug to handle players in the swamp -* It works like this: When the plyer enters teh swamp the spawnfunc_trigger_swamp -* attaches a new "swampslug" to the player. As long as the plyer is inside -* the swamp the swamp gives the slug new health. But the slug slowly kills itself -* so when the player goes outside the swamp, it dies and releases the player from the -* swamps curses (dmg/slowdown) -* -* I do it this way becuz there is no "untouch" event. -*/ -void swampslug_think(entity this) -{ - //Slowly kill the slug - this.health = this.health - 1; - - //Slug dead? then remove curses. - if(this.health <= 0) - { - this.owner.in_swamp = 0; - delete(this); - //centerprint(this.owner,"Killing slug...\n"); - return; - } - - // Slug still alive, so we are still in the swamp - // Or we have exited it very recently. - // Do the damage and renew the timer. -#ifdef SVQC - Damage (this.owner, this, this, this.dmg, DEATH_SWAMP.m_id, DMG_NOWEP, this.owner.origin, '0 0 0'); -#endif - - this.nextthink = time + this.swamp_interval; -} - -void swamp_touch(entity this, entity toucher) -{ - // If whatever thats touching the swamp is not a player - // or if its a dead player, just dont care abt it. - if(!IS_PLAYER(toucher) || IS_DEAD(toucher)) - return; - - EXACTTRIGGER_TOUCH(this, toucher); - - // Chech if player alredy got a swampslug. - if(toucher.in_swamp != 1) - { - // If not attach one. - //centerprint(toucher,"Entering swamp!\n"); - toucher.swampslug = spawn(); - toucher.swampslug.health = 2; - setthink(toucher.swampslug, swampslug_think); - toucher.swampslug.nextthink = time; - toucher.swampslug.owner = toucher; - toucher.swampslug.dmg = this.dmg; - toucher.swampslug.swamp_interval = this.swamp_interval; - toucher.swamp_slowdown = this.swamp_slowdown; - toucher.in_swamp = 1; - return; - } - - //toucher.in_swamp = 1; - - //Revitalize players swampslug - toucher.swampslug.health = 2; -} - -REGISTER_NET_LINKED(ENT_CLIENT_SWAMP) - -#ifdef SVQC -float swamp_send(entity this, entity to, float sf) -{ - WriteHeader(MSG_ENTITY, ENT_CLIENT_SWAMP); - - WriteByte(MSG_ENTITY, this.dmg); // can probably get away with using a single byte here - WriteByte(MSG_ENTITY, this.swamp_slowdown); - WriteByte(MSG_ENTITY, this.swamp_interval); - - trigger_common_write(this, false); - - return true; -} - -void swamp_link(entity this) -{ - trigger_link(this, swamp_send); -} - -/*QUAKED spawnfunc_trigger_swamp (.5 .5 .5) ? -Players gettin into the swamp will -get slowd down and damaged -*/ -spawnfunc(trigger_swamp) -{ - // Init stuff - trigger_init(this); - settouch(this, swamp_touch); - - // Setup default keys, if missing - if(this.dmg <= 0) - this.dmg = 5; - if(this.swamp_interval <= 0) - this.swamp_interval = 1; - if(this.swamp_slowdown <= 0) - this.swamp_slowdown = 0.5; - - swamp_link(this); -} - -#elif defined(CSQC) - -NET_HANDLE(ENT_CLIENT_SWAMP, bool isnew) -{ - this.dmg = ReadByte(); - this.swamp_slowdown = ReadByte(); - this.swamp_interval = ReadByte(); - - trigger_common_read(this, false); - - return = true; - - this.classname = "trigger_swamp"; - this.solid = SOLID_TRIGGER; - settouch(this, swamp_touch); - this.drawmask = MASK_NORMAL; - this.move_time = time; - this.entremove = trigger_remove_generic; -} -#endif diff --git a/qcsrc/common/triggers/trigger/swamp.qh b/qcsrc/common/triggers/trigger/swamp.qh deleted file mode 100644 index f4df98378..000000000 --- a/qcsrc/common/triggers/trigger/swamp.qh +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -.float swamp_interval; //Hurt players in swamp with this interval -.float swamp_slowdown; //Players in swamp get slowd down by this mutch 0-1 is slowdown 1-~ is speedup (!?) -.entity swampslug; - -.float in_swamp; // bool -.entity swampslug; // Uses this to release from swamp ("untouch" fix) diff --git a/qcsrc/common/triggers/trigger/teleport.qc b/qcsrc/common/triggers/trigger/teleport.qc deleted file mode 100644 index 825dd01dd..000000000 --- a/qcsrc/common/triggers/trigger/teleport.qc +++ /dev/null @@ -1,183 +0,0 @@ -#include "teleport.qh" -REGISTER_NET_LINKED(ENT_CLIENT_TRIGGER_TELEPORT) - -#ifdef SVQC -void trigger_teleport_use(entity this, entity actor, entity trigger) -{ - if(teamplay) - this.team = actor.team; -#ifdef SVQC - this.SendFlags |= SF_TRIGGER_UPDATE; -#endif -} -#endif - -bool Teleport_Active(entity this, entity player) -{ - if (this.active != ACTIVE_ACTIVE) - return false; - -#ifdef SVQC - if (!player.teleportable) - return false; - - if(player.vehicle) - if(!player.vehicle.teleportable) - return false; - - if(IS_TURRET(player)) - return false; -#elif defined(CSQC) - if(!IS_PLAYER(player)) - return false; -#endif - - if(IS_DEAD(player)) - return false; - - if(this.team) - if(((this.spawnflags & INVERT_TEAMS) == 0) == (DIFF_TEAM(this, player))) - return false; - - return true; -} - -void Teleport_Touch(entity this, entity toucher) -{ - entity player = toucher; - - if(!Teleport_Active(this, player)) - return; - - EXACTTRIGGER_TOUCH(this, player); - -#ifdef SVQC - if(IS_PLAYER(player)) - RemoveGrapplingHooks(player); -#endif - - entity e; - e = Simple_TeleportPlayer(this, player); - -#ifdef SVQC - string s = this.target; this.target = string_null; - SUB_UseTargets(this, player, player); // TODO: should we be using toucher for trigger too? - if (!this.target) this.target = s; - - SUB_UseTargets(e, player, player); -#endif -} - -#ifdef SVQC -void target_teleport_use(entity this, entity actor, entity trigger) -{ - entity player = actor; - - if(!Teleport_Active(this, player)) - return; - - if(IS_PLAYER(player)) - RemoveGrapplingHooks(player); - - entity e = Simple_TeleportPlayer(this, player); - - string s = this.target; this.target = string_null; - SUB_UseTargets(this, player, player); // TODO: should we be using toucher for trigger too? - if (!this.target) - { - this.target = s; - } - - SUB_UseTargets(e, player, player); -} -#endif - -#ifdef SVQC -float trigger_teleport_send(entity this, entity to, float sf) -{ - WriteHeader(MSG_ENTITY, ENT_CLIENT_TRIGGER_TELEPORT); - - WriteByte(MSG_ENTITY, this.team); - WriteInt24_t(MSG_ENTITY, this.spawnflags); - WriteByte(MSG_ENTITY, this.active); - WriteCoord(MSG_ENTITY, this.speed); - - trigger_common_write(this, true); - - return true; -} - -void trigger_teleport_link(entity this) -{ - //trigger_link(this, trigger_teleport_send); -} - -spawnfunc(trigger_teleport) -{ - this.angles = '0 0 0'; - - this.active = ACTIVE_ACTIVE; - //trigger_init(this); // only for predicted triggers? - EXACTTRIGGER_INIT; - this.use = trigger_teleport_use; - - if(this.noise != "") - FOREACH_WORD(this.noise, true, precache_sound(it)); - - // this must be called to spawn the teleport waypoints for bots - InitializeEntity(this, teleport_findtarget, INITPRIO_FINDTARGET); - - if (this.target == "") - { - objerror (this, "Teleporter with no target"); - return; - } - - IL_PUSH(g_teleporters, this); -} - -spawnfunc(target_teleporter) -{ - if(this.target == "") - { - // actually a destination! - spawnfunc_info_teleport_destination(this); - return; - } - - this.active = ACTIVE_ACTIVE; - - this.use = target_teleport_use; - - if(this.noise != "") - FOREACH_WORD(this.noise, true, precache_sound(it)); - - InitializeEntity(this, teleport_findtarget, INITPRIO_FINDTARGET); -} -#elif defined(CSQC) -NET_HANDLE(ENT_CLIENT_TRIGGER_TELEPORT, bool isnew) -{ - this.classname = "trigger_teleport"; - if(isnew) - IL_PUSH(g_teleporters, this); - int mytm = ReadByte(); - if(mytm) - { - this.team = mytm - 1; - } - this.spawnflags = ReadInt24_t(); - this.active = ReadByte(); - this.speed = ReadCoord(); - - trigger_common_read(this, true); - - this.entremove = trigger_remove_generic; - this.solid = SOLID_TRIGGER; - //settouch(this, trigger_push_touch); - this.move_time = time; - defer(this, 0.25, teleport_findtarget); - - return true; -} - -#endif diff --git a/qcsrc/common/triggers/trigger/teleport.qh b/qcsrc/common/triggers/trigger/teleport.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/common/triggers/trigger/teleport.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/common/triggers/trigger/viewloc.qc b/qcsrc/common/triggers/trigger/viewloc.qc deleted file mode 100644 index ba5dcbe44..000000000 --- a/qcsrc/common/triggers/trigger/viewloc.qc +++ /dev/null @@ -1,213 +0,0 @@ -#include "viewloc.qh" -#if defined(CSQC) -#elif defined(MENUQC) -#elif defined(SVQC) - #include - #include -#endif - -REGISTER_NET_LINKED(ENT_CLIENT_VIEWLOC) -REGISTER_NET_LINKED(ENT_CLIENT_VIEWLOC_TRIGGER) - -#ifdef SVQC - -void viewloc_think(entity this) -{ - // we abuse this method, rather than using normal .touch, because touch isn't reliable with multiple clients inside the same trigger, and can't "untouch" entities - - // set myself as current viewloc where possible -#if 1 - FOREACH_CLIENT(IS_PLAYER(it) && it.viewloc == this, - { - it.viewloc = NULL; - }); -#else - entity e; - for(e = NULL; (e = findentity(e, viewloc, this)); ) - e.viewloc = NULL; -#endif - -#if 1 - FOREACH_CLIENT(!it.viewloc && IS_PLAYER(it), - { - vector emin = it.absmin; - vector emax = it.absmax; - if(this.solid == SOLID_BSP) - { - emin -= '1 1 1'; - emax += '1 1 1'; - } - if(boxesoverlap(emin, emax, this.absmin, this.absmax)) // quick - { - if(WarpZoneLib_BoxTouchesBrush(emin, emax, this, it)) // accurate - it.viewloc = this; - } - }); -#else - - for(e = findradius((this.absmin + this.absmax) * 0.5, vlen(this.absmax - this.absmin) * 0.5 + 1); e; e = e.chain) - if(!e.viewloc) - if(IS_PLAYER(e)) // should we support non-player entities with this? - //if(!IS_DEAD(e)) // death view is handled separately, we can't override this just yet - { - vector emin = e.absmin; - vector emax = e.absmax; - if(this.solid == SOLID_BSP) - { - emin -= '1 1 1'; - emax += '1 1 1'; - } - if(boxesoverlap(emin, emax, this.absmin, this.absmax)) // quick - if(WarpZoneLib_BoxTouchesBrush(emin, emax, this, e)) // accurate - e.viewloc = this; - } -#endif - - this.nextthink = time; -} - -bool trigger_viewloc_send(entity this, entity to, int sf) -{ - // CSQC doesn't need to know our origin (yet), as we're only available for referencing - WriteHeader(MSG_ENTITY, ENT_CLIENT_VIEWLOC_TRIGGER); - - WriteByte(MSG_ENTITY, this.spawnflags); - - WriteEntity(MSG_ENTITY, this.enemy); - WriteEntity(MSG_ENTITY, this.goalentity); - - WriteVector(MSG_ENTITY, this.origin); - - return true; -} - -void viewloc_init(entity this) -{ - entity e; - for(e = NULL; (e = find(e, targetname, this.target)); ) - if(e.classname == "target_viewlocation_start") - { - this.enemy = e; - break; - } - for(e = NULL; (e = find(e, targetname, this.target2)); ) - if(e.classname == "target_viewlocation_end") - { - this.goalentity = e; - break; - } - - if(!this.enemy) { LOG_INFO("^1FAIL!"); delete(this); return; } - - if(!this.goalentity) - this.goalentity = this.enemy; // make them match so CSQC knows what to do - - Net_LinkEntity(this, false, 0, trigger_viewloc_send); - - setthink(this, viewloc_think); - this.nextthink = time; -} - -spawnfunc(trigger_viewlocation) -{ - // we won't check target2 here yet, as it may not even need to exist - if(this.target == "") { LOG_INFO("^1FAIL!"); delete(this); return; } - - EXACTTRIGGER_INIT; - InitializeEntity(this, viewloc_init, INITPRIO_FINDTARGET); -} - -bool viewloc_send(entity this, entity to, int sf) -{ - WriteHeader(MSG_ENTITY, ENT_CLIENT_VIEWLOC); - - WriteByte(MSG_ENTITY, this.cnt); - - WriteVector(MSG_ENTITY, this.origin); - - WriteAngle(MSG_ENTITY, this.angles_x); - WriteAngle(MSG_ENTITY, this.angles_y); - WriteAngle(MSG_ENTITY, this.angles_z); - - return true; -} - -.float angle; -void viewloc_link(entity this) -{ - if(this.angle) - this.angles_y = this.angle; - Net_LinkEntity(this, false, 0, viewloc_send); -} - -spawnfunc(target_viewlocation_start) -{ - this.classname = "target_viewlocation_start"; - this.cnt = 1; - viewloc_link(this); -} -spawnfunc(target_viewlocation_end) -{ - this.classname = "target_viewlocation_end"; - this.cnt = 2; - viewloc_link(this); -} - -// compatibility -spawnfunc(target_viewlocation) -{ - spawnfunc_target_viewlocation_start(this); -} - -#elif defined(CSQC) - -void trigger_viewloc_updatelink(entity this) -{ - this.enemy = findfloat(NULL, entnum, this.cnt); - this.goalentity = findfloat(NULL, entnum, this.count); -} - -NET_HANDLE(ENT_CLIENT_VIEWLOC_TRIGGER, bool isnew) -{ - this.spawnflags = ReadByte(); - - float point1 = ReadShort(); - float point2 = ReadShort(); - - this.enemy = findfloat(NULL, entnum, point1); - this.goalentity = findfloat(NULL, entnum, point2); - - this.origin = ReadVector(); - - return = true; - - setorigin(this, this.origin); - - this.cnt = point1; - this.count = point2; - - setthink(this, trigger_viewloc_updatelink); - this.nextthink = time + 1; // we need to delay this or else - - this.classname = "trigger_viewlocation"; - this.drawmask = MASK_NORMAL; // not so concerned, but better keep it alive -} - -NET_HANDLE(ENT_CLIENT_VIEWLOC, bool isnew) -{ - this.cnt = ReadByte(); - - this.origin = ReadVector(); - setorigin(this, this.origin); - - this.movedir_x = ReadAngle(); - this.movedir_y = ReadAngle(); - this.movedir_z = ReadAngle(); - - return = true; - - this.classname = ((this.cnt == 2) ? "target_viewlocation_end" : "target_viewlocation_start"); - this.drawmask = MASK_NORMAL; // don't cull it -} - -#endif diff --git a/qcsrc/common/triggers/trigger/viewloc.qh b/qcsrc/common/triggers/trigger/viewloc.qh deleted file mode 100644 index 3c393afd3..000000000 --- a/qcsrc/common/triggers/trigger/viewloc.qh +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - - -const int VIEWLOC_NOSIDESCROLL = BIT(0); // NOTE: currently unimplemented -const int VIEWLOC_FREEAIM = BIT(1); -const int VIEWLOC_FREEMOVE = BIT(2); - -.entity viewloc; - -#ifdef CSQC -.entity goalentity; -.entity enemy; -.vector movedir; -#endif diff --git a/qcsrc/common/triggers/triggers.qc b/qcsrc/common/triggers/triggers.qc deleted file mode 100644 index 9db38a10b..000000000 --- a/qcsrc/common/triggers/triggers.qc +++ /dev/null @@ -1,349 +0,0 @@ -#include "triggers.qh" -void SUB_DontUseTargets(entity this, entity actor, entity trigger) { } - -void SUB_UseTargets(entity this, entity actor, entity trigger); - -void DelayThink(entity this) -{ - SUB_UseTargets (this, this.enemy, NULL); - delete(this); -} - -void FixSize(entity e) -{ - e.mins_x = rint(e.mins_x); - e.mins_y = rint(e.mins_y); - e.mins_z = rint(e.mins_z); - - e.maxs_x = rint(e.maxs_x); - e.maxs_y = rint(e.maxs_y); - e.maxs_z = rint(e.maxs_z); -} - -#ifdef SVQC -void generic_setactive(entity this, int act) -{ - if(act == ACTIVE_TOGGLE) - { - if(this.active == ACTIVE_ACTIVE) - { - this.active = ACTIVE_NOT; - } - else - { - this.active = ACTIVE_ACTIVE; - } - } - else - { - this.active = act; - } -} - -void generic_netlinked_setactive(entity this, int act) -{ - int old_status = this.active; - generic_setactive(this, act); - - if (this.active != old_status) - { - this.SendFlags |= SF_TRIGGER_UPDATE; - } -} - -void generic_netlinked_reset(entity this) -{ - IFTARGETED - { - if(this.spawnflags & START_ENABLED) - { - this.active = ACTIVE_ACTIVE; - } - else - { - this.active = ACTIVE_NOT; - } - } - else - { - this.active = ACTIVE_ACTIVE; - } - - this.SendFlags |= SF_TRIGGER_UPDATE; -} - -// Compatibility with old maps -void generic_netlinked_legacy_use(entity this, entity actor, entity trigger) -{ - LOG_WARNF("Entity %s was (de)activated by a trigger, please update map to use relays", this.targetname); - this.setactive(this, ACTIVE_TOGGLE); -} - -bool autocvar_g_triggers_debug = true; - -void trigger_init(entity this) -{ - string m = this.model; - EXACTTRIGGER_INIT; - if(autocvar_g_triggers_debug) - { - if(m != "") - { - precache_model(m); - _setmodel(this, m); // no precision needed - } - setorigin(this, this.origin); - if(this.scale) - setsize(this, this.mins * this.scale, this.maxs * this.scale); - else - setsize(this, this.mins, this.maxs); - } - - if(autocvar_g_triggers_debug) - BITSET_ASSIGN(this.effects, EF_NODEPTHTEST); -} - -void trigger_link(entity this, bool(entity this, entity to, int sendflags) sendfunc) -{ - setSendEntity(this, sendfunc); - this.SendFlags = 0xFFFFFF; -} - -void trigger_common_write(entity this, bool withtarget) -{ - int f = 0; - if(this.warpzone_isboxy) - BITSET_ASSIGN(f, 1); - if(this.origin != '0 0 0') - BITSET_ASSIGN(f, 4); - if(this.movedir != '0 0 0') - BITSET_ASSIGN(f, 8); - if(this.angles != '0 0 0') - BITSET_ASSIGN(f, 16); - WriteByte(MSG_ENTITY, f); - - if(withtarget) - { - // probably some way to clean this up... - int targbits = 0; - if(this.target && this.target != "") targbits |= BIT(0); - if(this.target2 && this.target2 != "") targbits |= BIT(1); - if(this.target3 && this.target3 != "") targbits |= BIT(2); - if(this.target4 && this.target4 != "") targbits |= BIT(3); - if(this.targetname && this.targetname != "") targbits |= BIT(4); - if(this.killtarget && this.killtarget != "") targbits |= BIT(5); - - WriteByte(MSG_ENTITY, targbits); - - if(targbits & BIT(0)) - WriteString(MSG_ENTITY, this.target); - if(targbits & BIT(1)) - WriteString(MSG_ENTITY, this.target2); - if(targbits & BIT(2)) - WriteString(MSG_ENTITY, this.target3); - if(targbits & BIT(3)) - WriteString(MSG_ENTITY, this.target4); - if(targbits & BIT(4)) - WriteString(MSG_ENTITY, this.targetname); - if(targbits & BIT(5)) - WriteString(MSG_ENTITY, this.killtarget); - } - - if(f & 4) - WriteVector(MSG_ENTITY, this.origin); - - if(f & 8) - WriteVector(MSG_ENTITY, this.movedir); - - if(f & 16) - WriteVector(MSG_ENTITY, this.angles); - - WriteShort(MSG_ENTITY, this.modelindex); - WriteVector(MSG_ENTITY, this.mins); - WriteVector(MSG_ENTITY, this.maxs); - WriteByte(MSG_ENTITY, bound(1, this.scale * 16, 255)); -} - -#elif defined(CSQC) - -void trigger_common_read(entity this, bool withtarget) -{ - int f = ReadByte(); - this.warpzone_isboxy = (f & 1); - - if(withtarget) - { - strfree(this.target); - strfree(this.target2); - strfree(this.target3); - strfree(this.target4); - strfree(this.targetname); - strfree(this.killtarget); - - int targbits = ReadByte(); - - this.target = ((targbits & BIT(0)) ? strzone(ReadString()) : string_null); - this.target2 = ((targbits & BIT(1)) ? strzone(ReadString()) : string_null); - this.target3 = ((targbits & BIT(2)) ? strzone(ReadString()) : string_null); - this.target4 = ((targbits & BIT(3)) ? strzone(ReadString()) : string_null); - this.targetname = ((targbits & BIT(4)) ? strzone(ReadString()) : string_null); - this.killtarget = ((targbits & BIT(5)) ? strzone(ReadString()) : string_null); - } - - if(f & 4) - this.origin = ReadVector(); - else - this.origin = '0 0 0'; - setorigin(this, this.origin); - - if(f & 8) - this.movedir = ReadVector(); - else - this.movedir = '0 0 0'; - - if(f & 16) - this.angles = ReadVector(); - else - this.angles = '0 0 0'; - - this.modelindex = ReadShort(); - this.mins = ReadVector(); - this.maxs = ReadVector(); - this.scale = ReadByte() / 16; - setsize(this, this.mins, this.maxs); -} - -void trigger_remove_generic(entity this) -{ - strfree(this.target); - strfree(this.target2); - strfree(this.target3); - strfree(this.target4); - strfree(this.targetname); - strfree(this.killtarget); -} -#endif - - -/* -============================== -SUB_UseTargets - -the global "activator" should be set to the entity that initiated the firing. - -If this.delay is set, a DelayedUse entity will be created that will actually -do the SUB_UseTargets after that many seconds have passed. - -Centerprints any this.message to the activator. - -Removes all entities with a targetname that match this.killtarget, -and removes them, so some events can remove other triggers. - -Search for (string)targetname in all entities that -match (string)this.target and call their .use function - -============================== -*/ - -void SUB_UseTargets_Ex(entity this, entity actor, entity trigger, bool preventReuse) -{ -// -// check for a delay -// - if (this.delay) - { - // create a temp object to fire at a later time - entity t = new(DelayedUse); - t.nextthink = time + this.delay; - setthink(t, DelayThink); - t.enemy = actor; - t.message = this.message; - t.killtarget = this.killtarget; - t.target = this.target; - t.target2 = this.target2; - t.target3 = this.target3; - t.target4 = this.target4; - t.antiwall_flag = this.antiwall_flag; - return; - } - - string s; - -// -// print the message -// -#ifdef SVQC - if(this) - if(IS_PLAYER(actor) && this.message != "") - if(IS_REAL_CLIENT(actor)) - { - centerprint(actor, this.message); - if (this.noise == "") - play2(actor, SND(TALK)); - } - -// -// kill the killtagets -// - s = this.killtarget; - if (s != "") - { - for(entity t = NULL; (t = find(t, targetname, s)); ) - delete(t); - } -#endif - -// -// fire targets -// - - if(this.target_random) - RandomSelection_Init(); - - for(int i = 0; i < 4; ++i) - { - switch(i) - { - default: - case 0: s = this.target; break; - case 1: s = this.target2; break; - case 2: s = this.target3; break; - case 3: s = this.target4; break; - } - if (s != "") - { - // Flag to set func_clientwall state - // 1 == deactivate, 2 == activate, 0 == do nothing - int aw_flag = this.antiwall_flag; - for(entity t = NULL; (t = find(t, targetname, s)); ) - { - if(t.use && (t.sub_target_used != time || !preventReuse)) - { - if(this.target_random) - { - RandomSelection_AddEnt(t, 1, 0); - } - else - { - if (t.classname == "func_clientwall" || t.classname == "func_clientillusionary") - t.antiwall_flag = aw_flag; - - t.use(t, actor, this); - if(preventReuse) - t.sub_target_used = time; - } - } - } - } - } - - if(this.target_random && RandomSelection_chosen_ent) - { - RandomSelection_chosen_ent.use(RandomSelection_chosen_ent, actor, this); - if(preventReuse) - RandomSelection_chosen_ent.sub_target_used = time; - } -} - -void SUB_UseTargets(entity this, entity actor, entity trigger) { SUB_UseTargets_Ex(this, actor, trigger, false); } -void SUB_UseTargets_PreventReuse(entity this, entity actor, entity trigger) { SUB_UseTargets_Ex(this, actor, trigger, true); } diff --git a/qcsrc/common/triggers/triggers.qh b/qcsrc/common/triggers/triggers.qh deleted file mode 100644 index bfb32696a..000000000 --- a/qcsrc/common/triggers/triggers.qh +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once -#include "defs.qh" - -.bool pushable; - -.float antiwall_flag; // Variable to define what to do with func_clientwall -// 0 == do nothing, 1 == deactivate, 2 == activate - -.float height; - -#define IFTARGETED if(this.targetname && this.targetname != "") - -.float lip; - -// used elsewhere (will fix) -#ifdef SVQC -void trigger_common_write(entity this, bool withtarget); - -string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin); - -void target_voicescript_next(entity pl); -void target_voicescript_clear(entity pl); - -void SUB_UseTargets_PreventReuse(entity this, entity actor, entity trigger); - -void generic_setactive(entity this, int act); -// generic methods for netlinked entities -void generic_netlinked_reset(entity this); -void generic_netlinked_setactive(entity this, int act); -// WARNING: DON'T USE, ONLY TO KEEP COMPATIBILITY BECAUSE OF SWITCH FROM .state TO .alive!!!! -void generic_netlinked_legacy_use(entity this, entity actor, entity trigger); -#endif - -.float sub_target_used; - -.float volume, atten; - -.vector dest; - -void FixSize(entity e); - -#ifdef CSQC -void trigger_common_read(entity this, bool withtarget); -void trigger_remove_generic(entity this); - -.float active; -.string target; -.string targetname; -#endif diff --git a/qcsrc/common/util.qc b/qcsrc/common/util.qc index 95ab69ca6..183302b3a 100644 --- a/qcsrc/common/util.qc +++ b/qcsrc/common/util.qc @@ -2,7 +2,7 @@ #if defined(CSQC) #include "constants.qh" - #include "../client/mutators/events.qh" + #include #include "mapinfo.qh" #include "notifications/all.qh" #include "scores.qh" @@ -10,14 +10,136 @@ #elif defined(MENUQC) #elif defined(SVQC) #include "constants.qh" - #include "../server/mutators/events.qh" + #include #include "notifications/all.qh" #include #include "scores.qh" #include "mapinfo.qh" #endif +#ifdef SVQC +float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity) // returns the number of traces done, for benchmarking +{ + vector pos, dir, t; + float nudge; + entity stopentity; + + //nudge = 2 * cvar("collision_impactnudge"); // why not? + nudge = 0.5; + + dir = normalize(v2 - v1); + + pos = v1 + dir * nudge; + + float c; + c = 0; + + for (;;) + { + if(pos * dir >= v2 * dir) + { + // went too far + trace_fraction = 1; + trace_endpos = v2; + return c; + } + + tracebox(pos, mi, ma, v2, nomonsters, forent); + ++c; + + if(c == 50) + { + LOG_TRACE("When tracing from ", vtos(v1), " to ", vtos(v2)); + LOG_TRACE(" Nudging gets us nowhere at ", vtos(pos)); + LOG_TRACE(" trace_endpos is ", vtos(trace_endpos)); + LOG_TRACE(" trace distance is ", ftos(vlen(pos - trace_endpos))); + } + + stopentity = trace_ent; + + if(trace_startsolid) + { + // we started inside solid. + // then trace from endpos to pos + t = trace_endpos; + tracebox(t, mi, ma, pos, nomonsters, forent); + ++c; + if(trace_startsolid) + { + // t is still inside solid? bad + // force advance, then, and retry + pos = t + dir * nudge; + + // but if we hit an entity, stop RIGHT before it + if(stopatentity && stopentity && stopentity != ignorestopatentity) + { + trace_ent = stopentity; + trace_endpos = t; + trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir); + return c; + } + } + else + { + // we actually LEFT solid! + trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir); + return c; + } + } + else + { + // pos is outside solid?!? but why?!? never mind, just return it. + trace_endpos = pos; + trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir); + return c; + } + } +} + +void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity) +{ + tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity, ignorestopatentity); +} +#endif + #ifdef GAMEQC +/* +================== +findbetterlocation + +Returns a point at least 12 units away from walls +(useful for explosion animations, although the blast is performed where it really happened) +Ripped from DPMod +================== +*/ +vector findbetterlocation (vector org, float mindist) +{ + vector vec = mindist * '1 0 0'; + int c = 0; + while (c < 6) + { + traceline (org, org + vec, true, NULL); + vec = vec * -1; + if (trace_fraction < 1) + { + vector loc = trace_endpos; + traceline (loc, loc + vec, true, NULL); + if (trace_fraction >= 1) + org = loc + vec; + } + if (c & 1) + { + float h = vec.y; + vec.y = vec.x; + vec.x = vec.z; + vec.z = h; + } + c = c + 1; + } + + return org; +} + /* * Get "real" origin, in worldspace, even if ent is attached to something else. */ diff --git a/qcsrc/common/util.qh b/qcsrc/common/util.qh index 3304d0e7a..a1c0d6785 100644 --- a/qcsrc/common/util.qh +++ b/qcsrc/common/util.qh @@ -1,6 +1,27 @@ #pragma once +#ifdef SVQC + #include +#endif + +#ifdef SVQC +float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity); // returns the number of traces done, for benchmarking + +void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity); +#endif + #ifdef GAMEQC +/* +================== +findbetterlocation + +Returns a point at least 12 units away from walls +(useful for explosion animations, although the blast is performed where it really happened) +Ripped from DPMod +================== +*/ +vector findbetterlocation (vector org, float mindist); + vector real_origin(entity ent); #endif diff --git a/qcsrc/common/vehicles/vehicle/racer.qc b/qcsrc/common/vehicles/vehicle/racer.qc index 70ee753e0..93ed6d31d 100644 --- a/qcsrc/common/vehicles/vehicle/racer.qc +++ b/qcsrc/common/vehicles/vehicle/racer.qc @@ -1,7 +1,7 @@ #include "racer.qh" #ifdef SVQC -#include +#include bool autocvar_g_vehicle_racer = true; @@ -137,16 +137,16 @@ void racer_align4point(entity this, float _delta) this.angles_z *= 1 - (autocvar_g_vehicle_racer_anglestabilizer * _delta); } -void racer_fire_rocket_aim(entity player, string tagname, entity trg) +void racer_fire_rocket_aim(entity this, entity player, string tagname, entity trg) { - entity racer = player.vehicle; - vector v = gettaginfo(racer, gettagindex(racer, tagname)); + vector v = gettaginfo(this, gettagindex(this, tagname)); racer_fire_rocket(player, v, v_forward, trg); } bool racer_frame(entity this, float dt) { - entity vehic = this.vehicle; + entity player = this; + entity vehic = player.vehicle; return = true; if(game_stopped) @@ -157,28 +157,27 @@ bool racer_frame(entity this, float dt) return; } - vehicles_frame(vehic, this); + vehicles_frame(vehic, player); - traceline(vehic.origin, vehic.origin + '0 0 1', MOVE_NOMONSTERS, this); - int cont = trace_dpstartcontents; + int cont = Mod_Q1BSP_SuperContentsFromNativeContents(pointcontents(vehic.origin)); if(!(cont & DPCONTENTS_WATER)) vehic.air_finished = time + autocvar_g_vehicle_racer_water_time; if(IS_DEAD(vehic)) { - PHYS_INPUT_BUTTON_ATCK(this) = PHYS_INPUT_BUTTON_ATCK2(this) = false; + PHYS_INPUT_BUTTON_ATCK(player) = PHYS_INPUT_BUTTON_ATCK2(player) = false; return; } racer_align4point(vehic, dt); - PHYS_INPUT_BUTTON_ZOOM(this) = PHYS_INPUT_BUTTON_CROUCH(this) = false; + PHYS_INPUT_BUTTON_ZOOM(player) = PHYS_INPUT_BUTTON_CROUCH(player) = false; vehic.angles_x *= -1; // Yaw float ftmp = autocvar_g_vehicle_racer_turnspeed * dt; - ftmp = bound(-ftmp, shortangle_f(this.v_angle_y - vehic.angles_y, vehic.angles_y), ftmp); + ftmp = bound(-ftmp, shortangle_f(player.v_angle_y - vehic.angles_y, vehic.angles_y), ftmp); vehic.angles_y = anglemods(vehic.angles_y + ftmp); // Roll @@ -186,7 +185,7 @@ bool racer_frame(entity this, float dt) // Pitch ftmp = autocvar_g_vehicle_racer_pitchspeed * dt; - ftmp = bound(-ftmp, shortangle_f(this.v_angle_x - vehic.angles_x, vehic.angles_x), ftmp); + ftmp = bound(-ftmp, shortangle_f(player.v_angle_x - vehic.angles_x, vehic.angles_x), ftmp); vehic.angles_x = bound(-autocvar_g_vehicle_racer_pitchlimit, anglemods(vehic.angles_x + ftmp), autocvar_g_vehicle_racer_pitchlimit); makevectors(vehic.angles); @@ -196,17 +195,17 @@ bool racer_frame(entity this, float dt) vector df = vehic.velocity * -autocvar_g_vehicle_racer_friction; //vehic.velocity_z = ftmp; - if(CS(this).movement) + if(CS(player).movement) { if(cont & DPCONTENTS_LIQUIDSMASK) { - if(CS(this).movement_x) { df += v_forward * ((CS(this).movement_x > 0) ? autocvar_g_vehicle_racer_water_speed_forward : -autocvar_g_vehicle_racer_water_speed_forward); } - if(CS(this).movement_y) { df += v_right * ((CS(this).movement_y > 0) ? autocvar_g_vehicle_racer_water_speed_strafe : -autocvar_g_vehicle_racer_water_speed_strafe); } + if(CS(player).movement_x) { df += v_forward * ((CS(player).movement_x > 0) ? autocvar_g_vehicle_racer_water_speed_forward : -autocvar_g_vehicle_racer_water_speed_forward); } + if(CS(player).movement_y) { df += v_right * ((CS(player).movement_y > 0) ? autocvar_g_vehicle_racer_water_speed_strafe : -autocvar_g_vehicle_racer_water_speed_strafe); } } else { - if(CS(this).movement_x) { df += v_forward * ((CS(this).movement_x > 0) ? autocvar_g_vehicle_racer_speed_forward : -autocvar_g_vehicle_racer_speed_forward); } - if(CS(this).movement_y) { df += v_right * ((CS(this).movement_y > 0) ? autocvar_g_vehicle_racer_speed_strafe : -autocvar_g_vehicle_racer_speed_strafe); } + if(CS(player).movement_x) { df += v_forward * ((CS(player).movement_x > 0) ? autocvar_g_vehicle_racer_speed_forward : -autocvar_g_vehicle_racer_speed_forward); } + if(CS(player).movement_y) { df += v_right * ((CS(player).movement_y > 0) ? autocvar_g_vehicle_racer_speed_strafe : -autocvar_g_vehicle_racer_speed_strafe); } } #ifdef SVQC @@ -231,7 +230,7 @@ bool racer_frame(entity this, float dt) #endif // Afterburn - if (PHYS_INPUT_BUTTON_JUMP(this) && vehic.vehicle_energy >= (autocvar_g_vehicle_racer_afterburn_cost * dt)) + if (PHYS_INPUT_BUTTON_JUMP(player) && vehic.vehicle_energy >= (autocvar_g_vehicle_racer_afterburn_cost * dt)) { #ifdef SVQC if(time - vehic.wait > 0.2) @@ -282,14 +281,14 @@ bool racer_frame(entity this, float dt) dforce = autocvar_g_vehicle_racer_water_downforce; df -= v_up * (vlen(vehic.velocity) * dforce); - CS(this).movement = vehic.velocity += df * dt; + CS(player).movement = vehic.velocity += df * dt; #ifdef SVQC Weapon wep1 = WEP_RACER; .entity weaponentity = weaponentities[0]; // TODO: unhardcode - if (!forbidWeaponUse(this)) - if (PHYS_INPUT_BUTTON_ATCK(this)) + if (!forbidWeaponUse(player)) + if (PHYS_INPUT_BUTTON_ATCK(player)) if (wep1.wr_checkammo1(wep1, vehic, weaponentity)) { string tagname = (vehic.cnt) @@ -299,7 +298,7 @@ bool racer_frame(entity this, float dt) w_shotorg = org; w_shotdir = v_forward; // Fix z-aim (for chase mode) - crosshair_trace(this); + crosshair_trace(player); w_shotdir.z = normalize(trace_endpos - org).z * 0.5; wep1.wr_think(wep1, vehic, weaponentity, 1); } @@ -308,7 +307,7 @@ bool racer_frame(entity this, float dt) { if(time >= vehic.vehicle_last_trace) { - crosshair_trace(this); + crosshair_trace(player); vehicles_locktarget(vehic, (1 / autocvar_g_vehicle_racer_rocket_locking_time) * dt, (1 / autocvar_g_vehicle_racer_rocket_locking_releasetime) * dt, @@ -320,63 +319,63 @@ bool racer_frame(entity this, float dt) if(vehic.lock_target) { if(vehic.lock_strength == 1) - UpdateAuxiliaryXhair(this, real_origin(vehic.lock_target), '1 0 0', 0); + UpdateAuxiliaryXhair(player, real_origin(vehic.lock_target), '1 0 0', 0); else if(vehic.lock_strength > 0.5) - UpdateAuxiliaryXhair(this, real_origin(vehic.lock_target), '0 1 0', 0); + UpdateAuxiliaryXhair(player, real_origin(vehic.lock_target), '0 1 0', 0); else if(vehic.lock_strength < 0.5) - UpdateAuxiliaryXhair(this, real_origin(vehic.lock_target), '0 0 1', 0); + UpdateAuxiliaryXhair(player, real_origin(vehic.lock_target), '0 0 1', 0); } } - if(!forbidWeaponUse(this)) + if(!forbidWeaponUse(player)) if(time > vehic.delay) - if(PHYS_INPUT_BUTTON_ATCK2(this)) + if(PHYS_INPUT_BUTTON_ATCK2(player)) { vehic.misc_bulletcounter += 1; vehic.delay = time + 0.3; if(vehic.misc_bulletcounter == 1) { - racer_fire_rocket_aim(this, "tag_rocket_r", (vehic.lock_strength == 1 && vehic.lock_target) ? vehic.lock_target : NULL); - this.vehicle_ammo2 = 50; + racer_fire_rocket_aim(vehic, player, "tag_rocket_r", (vehic.lock_strength == 1 && vehic.lock_target) ? vehic.lock_target : NULL); + player.vehicle_ammo2 = 50; } else if(vehic.misc_bulletcounter == 2) { - racer_fire_rocket_aim(this, "tag_rocket_l", (vehic.lock_strength == 1 && vehic.lock_target) ? vehic.lock_target : NULL); + racer_fire_rocket_aim(vehic, player, "tag_rocket_l", (vehic.lock_strength == 1 && vehic.lock_target) ? vehic.lock_target : NULL); vehic.lock_strength = 0; vehic.lock_target = NULL; vehic.misc_bulletcounter = 0; vehic.delay = time + autocvar_g_vehicle_racer_rocket_refire; vehic.lip = time; - this.vehicle_ammo2 = 0; + player.vehicle_ammo2 = 0; } } else if(vehic.misc_bulletcounter == 0) - this.vehicle_ammo2 = 100; + player.vehicle_ammo2 = 100; - this.vehicle_reload2 = bound(0, 100 * ((time - vehic.lip) / (vehic.delay - vehic.lip)), 100); + player.vehicle_reload2 = bound(0, 100 * ((time - vehic.lip) / (vehic.delay - vehic.lip)), 100); - if(vehic.vehicle_flags & VHF_SHIELDREGEN) + if(vehic.vehicle_flags & VHF_SHIELDREGEN) vehicles_regen(vehic, vehic.dmg_time, vehicle_shield, autocvar_g_vehicle_racer_shield, autocvar_g_vehicle_racer_shield_regen_pause, autocvar_g_vehicle_racer_shield_regen, dt, true); - if(vehic.vehicle_flags & VHF_HEALTHREGEN) + if(vehic.vehicle_flags & VHF_HEALTHREGEN) vehicles_regen(vehic, vehic.dmg_time, vehicle_health, autocvar_g_vehicle_racer_health, autocvar_g_vehicle_racer_health_regen_pause, autocvar_g_vehicle_racer_health_regen, dt, false); - if(vehic.vehicle_flags & VHF_ENERGYREGEN) + if(vehic.vehicle_flags & VHF_ENERGYREGEN) vehicles_regen(vehic, vehic.wait, vehicle_energy, autocvar_g_vehicle_racer_energy, autocvar_g_vehicle_racer_energy_regen_pause, autocvar_g_vehicle_racer_energy_regen, dt, false); - VEHICLE_UPDATE_PLAYER(this, vehic, health, racer); - VEHICLE_UPDATE_PLAYER(this, vehic, energy, racer); + VEHICLE_UPDATE_PLAYER(player, vehic, health, racer); + VEHICLE_UPDATE_PLAYER(player, vehic, energy, racer); if(vehic.vehicle_flags & VHF_HASSHIELD) - VEHICLE_UPDATE_PLAYER(this, vehic, shield, racer); + VEHICLE_UPDATE_PLAYER(player, vehic, shield, racer); - PHYS_INPUT_BUTTON_ATCK(this) = PHYS_INPUT_BUTTON_ATCK2(this) = false; + PHYS_INPUT_BUTTON_ATCK(player) = PHYS_INPUT_BUTTON_ATCK2(player) = false; #endif - setorigin(this, vehic.origin + '0 0 32'); - this.oldorigin = this.origin; // negate fall damage - this.velocity = vehic.velocity; + setorigin(player, vehic.origin + '0 0 32'); + player.oldorigin = player.origin; // negate fall damage + player.velocity = vehic.velocity; } void racer_think(entity this) diff --git a/qcsrc/common/weapons/weapon/devastator.qc b/qcsrc/common/weapons/weapon/devastator.qc index 0a046389c..c53e110fd 100644 --- a/qcsrc/common/weapons/weapon/devastator.qc +++ b/qcsrc/common/weapons/weapon/devastator.qc @@ -235,11 +235,19 @@ void W_Devastator_Think(entity this) else f = 1; + vector md = this.realowner.(weaponentity).movedir; + vector vecs = ((md.x > 0) ? md : '0 0 0'); + + vector dv = v_right * -vecs.y + v_up * vecs.z; + + if(!W_DualWielding(this.realowner)) + dv = '0 0 0'; // don't override! + velspeed = vlen(this.velocity); makevectors(this.realowner.v_angle); desireddir = WarpZone_RefSys_TransformVelocity(this.realowner, this, v_forward); - desiredorigin = WarpZone_RefSys_TransformOrigin(this.realowner, this, this.realowner.origin + this.realowner.view_ofs); + desiredorigin = WarpZone_RefSys_TransformOrigin(this.realowner, this, this.realowner.origin + this.realowner.view_ofs + dv); olddir = normalize(this.velocity); // now it gets tricky... we want to move like some curve to approximate the target direction diff --git a/qcsrc/common/weapons/weapon/devastator.qh b/qcsrc/common/weapons/weapon/devastator.qh index 0e8d8b2fb..e858d54e4 100644 --- a/qcsrc/common/weapons/weapon/devastator.qh +++ b/qcsrc/common/weapons/weapon/devastator.qh @@ -4,7 +4,7 @@ CLASS(Devastator, Weapon) /* spawnfunc */ ATTRIB(Devastator, m_canonical_spawnfunc, string, "weapon_devastator"); /* ammotype */ ATTRIB(Devastator, ammo_type, int, RESOURCE_ROCKETS); /* impulse */ ATTRIB(Devastator, impulse, int, 9); -/* flags */ ATTRIB(Devastator, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH | WEP_FLAG_NODUAL); +/* flags */ ATTRIB(Devastator, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH); /* rating */ ATTRIB(Devastator, bot_pickupbasevalue, float, 8000); /* color */ ATTRIB(Devastator, wpcolor, vector, '1 1 0'); /* modelname */ ATTRIB(Devastator, mdl, string, "rl"); diff --git a/qcsrc/common/weapons/weapon/fireball.qc b/qcsrc/common/weapons/weapon/fireball.qc index ef2eb91ca..ee4b2e084 100644 --- a/qcsrc/common/weapons/weapon/fireball.qc +++ b/qcsrc/common/weapons/weapon/fireball.qc @@ -155,7 +155,6 @@ void W_Fireball_Attack1(entity actor, .entity weaponentity) PROJECTILE_MAKETRIGGER(proj); proj.projectiledeathtype = WEP_FIREBALL.m_id; proj.weaponentity_fld = weaponentity; - proj.weaponentity_fld = weaponentity; setorigin(proj, w_shotorg); set_movetype(proj, MOVETYPE_FLY); diff --git a/qcsrc/common/weapons/weapon/mortar.qh b/qcsrc/common/weapons/weapon/mortar.qh index 2161d468b..affec0dbc 100644 --- a/qcsrc/common/weapons/weapon/mortar.qh +++ b/qcsrc/common/weapons/weapon/mortar.qh @@ -4,7 +4,7 @@ CLASS(Mortar, Weapon) /* spawnfunc */ ATTRIB(Mortar, m_canonical_spawnfunc, string, "weapon_mortar"); /* ammotype */ ATTRIB(Mortar, ammo_type, int, RESOURCE_ROCKETS); /* impulse */ ATTRIB(Mortar, impulse, int, 4); -/* flags */ ATTRIB(Mortar, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH | WEP_FLAG_NODUAL); +/* flags */ ATTRIB(Mortar, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH); /* rating */ ATTRIB(Mortar, bot_pickupbasevalue, float, 7000); /* color */ ATTRIB(Mortar, wpcolor, vector, '1 0 0'); /* modelname */ ATTRIB(Mortar, mdl, string, "gl"); diff --git a/qcsrc/common/weapons/weapon/porto.qc b/qcsrc/common/weapons/weapon/porto.qc index ca460f089..9f79bf440 100644 --- a/qcsrc/common/weapons/weapon/porto.qc +++ b/qcsrc/common/weapons/weapon/porto.qc @@ -1,7 +1,7 @@ #include "porto.qh" #ifdef SVQC -#include +#include REGISTER_MUTATOR(porto_ticker, true); MUTATOR_HOOKFUNCTION(porto_ticker, SV_StartFrame) { diff --git a/qcsrc/common/weapons/weapon/rifle.qc b/qcsrc/common/weapons/weapon/rifle.qc index 1740c45d8..d6996042d 100644 --- a/qcsrc/common/weapons/weapon/rifle.qc +++ b/qcsrc/common/weapons/weapon/rifle.qc @@ -159,7 +159,11 @@ METHOD(Rifle, wr_checkammo2, bool(entity thiswep, entity actor, .entity weaponen } METHOD(Rifle, wr_resetplayer, void(entity thiswep, entity actor)) { - actor.rifle_accumulator = time - WEP_CVAR(rifle, bursttime); + for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + .entity weaponentity = weaponentities[slot]; + actor.(weaponentity).rifle_accumulator = time - WEP_CVAR(rifle, bursttime); + } } METHOD(Rifle, wr_reload, void(entity thiswep, entity actor, .entity weaponentity)) { diff --git a/qcsrc/common/weapons/weapon/rifle.qh b/qcsrc/common/weapons/weapon/rifle.qh index 87bc4d7ec..560354c05 100644 --- a/qcsrc/common/weapons/weapon/rifle.qh +++ b/qcsrc/common/weapons/weapon/rifle.qh @@ -4,7 +4,7 @@ CLASS(Rifle, Weapon) /* spawnfunc */ ATTRIB(Rifle, m_canonical_spawnfunc, string, "weapon_rifle"); /* ammotype */ ATTRIB(Rifle, ammo_type, int, RESOURCE_BULLETS); /* impulse */ ATTRIB(Rifle, impulse, int, 7); -/* flags */ ATTRIB(Rifle, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_PENETRATEWALLS | WEP_FLAG_NODUAL); +/* flags */ ATTRIB(Rifle, spawnflags, int, WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_PENETRATEWALLS); /* rating */ ATTRIB(Rifle, bot_pickupbasevalue, float, 7000); /* color */ ATTRIB(Rifle, wpcolor, vector, '0.5 1 0'); /* modelname */ ATTRIB(Rifle, mdl, string, "campingrifle"); diff --git a/qcsrc/common/weapons/weapon/vortex.qh b/qcsrc/common/weapons/weapon/vortex.qh index cc975cceb..8a11b2e13 100644 --- a/qcsrc/common/weapons/weapon/vortex.qh +++ b/qcsrc/common/weapons/weapon/vortex.qh @@ -4,7 +4,7 @@ CLASS(Vortex, Weapon) /* spawnfunc */ ATTRIB(Vortex, m_canonical_spawnfunc, string, "weapon_vortex"); /* ammotype */ ATTRIB(Vortex, ammo_type, int, RESOURCE_CELLS); /* impulse */ ATTRIB(Vortex, impulse, int, 7); -/* flags */ ATTRIB(Vortex, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_NODUAL); +/* flags */ ATTRIB(Vortex, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN); /* rating */ ATTRIB(Vortex, bot_pickupbasevalue, float, 8000); /* color */ ATTRIB(Vortex, wpcolor, vector, '0.5 1 1'); /* modelname */ ATTRIB(Vortex, mdl, string, "nex"); diff --git a/qcsrc/lib/warpzone/server.qc b/qcsrc/lib/warpzone/server.qc index ce4535452..2805c0050 100644 --- a/qcsrc/lib/warpzone/server.qc +++ b/qcsrc/lib/warpzone/server.qc @@ -6,7 +6,7 @@ #elif defined(SVQC) #include #include - #include + #include #include #include #include diff --git a/qcsrc/menu/command/menu_cmd.qc b/qcsrc/menu/command/menu_cmd.qc index e727b39e6..bd45230c9 100644 --- a/qcsrc/menu/command/menu_cmd.qc +++ b/qcsrc/menu/command/menu_cmd.qc @@ -3,7 +3,7 @@ #include "../menu.qh" #include "../item.qh" -#include "../mutators/events.qh" +#include #include diff --git a/qcsrc/menu/mutators/_mod.inc b/qcsrc/menu/mutators/_mod.inc index 98fb4815c..269809269 100644 --- a/qcsrc/menu/mutators/_mod.inc +++ b/qcsrc/menu/mutators/_mod.inc @@ -1 +1,2 @@ // generated file; do not modify +#include diff --git a/qcsrc/menu/mutators/_mod.qh b/qcsrc/menu/mutators/_mod.qh index 98fb4815c..93432be42 100644 --- a/qcsrc/menu/mutators/_mod.qh +++ b/qcsrc/menu/mutators/_mod.qh @@ -1 +1,2 @@ // generated file; do not modify +#include diff --git a/qcsrc/menu/mutators/events.qc b/qcsrc/menu/mutators/events.qc new file mode 100644 index 000000000..c2dbb7021 --- /dev/null +++ b/qcsrc/menu/mutators/events.qc @@ -0,0 +1 @@ +#include "events.qh" diff --git a/qcsrc/menu/xonotic/mainwindow.qc b/qcsrc/menu/xonotic/mainwindow.qc index 0afd27c8d..110b796be 100644 --- a/qcsrc/menu/xonotic/mainwindow.qc +++ b/qcsrc/menu/xonotic/mainwindow.qc @@ -1,6 +1,6 @@ #include "mainwindow.qh" -#include "../mutators/events.qh" +#include #include "nexposee.qh" #include "inputbox.qh" diff --git a/qcsrc/server/_mod.inc b/qcsrc/server/_mod.inc index 569301c5d..0e6ec096d 100644 --- a/qcsrc/server/_mod.inc +++ b/qcsrc/server/_mod.inc @@ -6,9 +6,6 @@ #include #include #include -#include -#include -#include #include #include #include diff --git a/qcsrc/server/_mod.qh b/qcsrc/server/_mod.qh index 2013fd6bb..a897b456a 100644 --- a/qcsrc/server/_mod.qh +++ b/qcsrc/server/_mod.qh @@ -6,9 +6,6 @@ #include #include #include -#include -#include -#include #include #include #include diff --git a/qcsrc/server/antilag.qc b/qcsrc/server/antilag.qc index 4062f7f66..c6e26e09e 100644 --- a/qcsrc/server/antilag.qc +++ b/qcsrc/server/antilag.qc @@ -5,6 +5,7 @@ #include #include #include + #include #include "antilag.qh" #endif @@ -146,3 +147,78 @@ void antilag_restore_all(entity ignore) antilag_restore(it, it); }); } + +/* +================== +traceline_antilag + +A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack +Additionally it moves players back into the past before the trace and restores them afterward. +================== +*/ +void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz) +{ + // check whether antilagged traces are enabled + if (lag < 0.001) + lag = 0; + if (!IS_REAL_CLIENT(forent)) + lag = 0; // only antilag for clients + + // change shooter to SOLID_BBOX so the shot can hit corpses + int oldsolid = source.dphitcontentsmask; + if(source) + source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE; + + if (lag) + antilag_takeback_all(forent, lag); + + // do the trace + if(wz) + WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent); + else + tracebox (v1, mi, ma, v2, nomonst, forent); + + // restore players to current positions + if (lag) + antilag_restore_all(forent); + + // restore shooter solid type + if(source) + source.dphitcontentsmask = oldsolid; +} +void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag) +{ + tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, false); +} +void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag) +{ + bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false); + if (autocvar_g_antilag != 2 || noantilag) + lag = 0; + traceline_antilag_force(source, v1, v2, nomonst, forent, lag); +} +void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag) +{ + bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false); + if (autocvar_g_antilag != 2 || noantilag) + lag = 0; + tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, false); +} +void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag) +{ + tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, true); +} +void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag) +{ + bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false); + if (autocvar_g_antilag != 2 || noantilag) + lag = 0; + WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag); +} +void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag) +{ + bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false); + if (autocvar_g_antilag != 2 || noantilag) + lag = 0; + tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, true); +} diff --git a/qcsrc/server/antilag.qh b/qcsrc/server/antilag.qh index d57762ccd..c3be5553a 100644 --- a/qcsrc/server/antilag.qh +++ b/qcsrc/server/antilag.qh @@ -13,3 +13,19 @@ void antilag_restore_all(entity ignore); #define ANTILAG_LATENCY(e) min(0.4, CS(e).ping * 0.001) // add one ticrate? + +/* +================== +traceline_antilag + +A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack +Additionally it moves players back into the past before the trace and restores them afterward. +================== +*/ +void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz); +void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag); +void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag); +void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag); +void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag); +void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag); +void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag); diff --git a/qcsrc/server/autocvars.qh b/qcsrc/server/autocvars.qh index c1abd0041..7d73a73a8 100644 --- a/qcsrc/server/autocvars.qh +++ b/qcsrc/server/autocvars.qh @@ -257,7 +257,6 @@ bool autocvar_lastlevel; //int autocvar_leadlimit; int autocvar_leadlimit_and_fraglimit; int autocvar_leadlimit_override; -int autocvar_loddebug; int autocvar_minplayers; string autocvar_nextmap; string autocvar_quit_and_redirect; diff --git a/qcsrc/server/bot/default/aim.qc b/qcsrc/server/bot/default/aim.qc index 383ead194..3a9befde4 100644 --- a/qcsrc/server/bot/default/aim.qc +++ b/qcsrc/server/bot/default/aim.qc @@ -11,7 +11,7 @@ #include "../../weapons/weaponsystem.qh" -#include "../../mutators/_mod.qh" +#include // traces multiple trajectories to find one that will impact the target // 'end' vector is the place it aims for, diff --git a/qcsrc/server/bot/default/bot.qc b/qcsrc/server/bot/default/bot.qc index b4272e3e0..976d67ec9 100644 --- a/qcsrc/server/bot/default/bot.qc +++ b/qcsrc/server/bot/default/bot.qc @@ -21,7 +21,7 @@ #include "../../race.qh" #include -#include "../../mutators/_mod.qh" +#include #include "../../weapons/accuracy.qh" diff --git a/qcsrc/server/bot/default/havocbot/havocbot.qc b/qcsrc/server/bot/default/havocbot/havocbot.qc index f52974020..91b5c463d 100644 --- a/qcsrc/server/bot/default/havocbot/havocbot.qc +++ b/qcsrc/server/bot/default/havocbot/havocbot.qc @@ -18,8 +18,8 @@ #include #include -#include -#include +#include +#include #include diff --git a/qcsrc/server/bot/default/havocbot/roles.qc b/qcsrc/server/bot/default/havocbot/roles.qc index 4c70c1b1b..2aa11b52c 100644 --- a/qcsrc/server/bot/default/havocbot/roles.qc +++ b/qcsrc/server/bot/default/havocbot/roles.qc @@ -3,6 +3,7 @@ #include #include #include +#include #include "havocbot.qh" #include "../cvars.qh" @@ -52,11 +53,11 @@ bool havocbot_goalrating_item_can_be_left_to_teammate(entity this, entity player if (item.health && player.health <= this.health) {return true;} if (item.armorvalue && player.armorvalue <= this.armorvalue) {return true;} if (STAT(WEAPONS, item) && !(STAT(WEAPONS, player) & STAT(WEAPONS, item))) {return true;} - if (item.ammo_shells && player.ammo_shells <= this.ammo_shells) {return true;} - if (item.ammo_nails && player.ammo_nails <= this.ammo_nails) {return true;} - if (item.ammo_rockets && player.ammo_rockets <= this.ammo_rockets) {return true;} - if (item.ammo_cells && player.ammo_cells <= this.ammo_cells) {return true;} - if (item.ammo_plasma && player.ammo_plasma <= this.ammo_plasma) {return true;} + if (item.ammo_shells && GetResourceAmount(player, RESOURCE_SHELLS) <= GetResourceAmount(this, RESOURCE_SHELLS)) {return true;} + if (item.ammo_nails && GetResourceAmount(player, RESOURCE_BULLETS) <= GetResourceAmount(this, RESOURCE_BULLETS)) {return true;} + if (item.ammo_rockets && GetResourceAmount(player, RESOURCE_ROCKETS) <= GetResourceAmount(this, RESOURCE_ROCKETS)) {return true;} + if (item.ammo_cells && GetResourceAmount(player, RESOURCE_CELLS) <= GetResourceAmount(this, RESOURCE_CELLS)) {return true;} + if (item.ammo_plasma && GetResourceAmount(player, RESOURCE_PLASMA) <= GetResourceAmount(this, RESOURCE_PLASMA)) {return true;} if (item.itemdef.instanceOfPowerup) {return true;} return false; diff --git a/qcsrc/server/bot/default/navigation.qc b/qcsrc/server/bot/default/navigation.qc index 36f25d79f..9334f011a 100644 --- a/qcsrc/server/bot/default/navigation.qc +++ b/qcsrc/server/bot/default/navigation.qc @@ -13,7 +13,7 @@ #include #include -#include +#include .float speed; diff --git a/qcsrc/server/cheats.qc b/qcsrc/server/cheats.qc index 22e77b4ea..f71cb494b 100644 --- a/qcsrc/server/cheats.qc +++ b/qcsrc/server/cheats.qc @@ -3,12 +3,13 @@ #include #include #include +#include #include "g_damage.qh" #include "race.qh" -#include "../common/triggers/teleporters.qh" +#include "../common/mapobjects/teleporters.qh" -#include "mutators/_mod.qh" +#include #include "weapons/tracing.qh" @@ -22,9 +23,10 @@ #include -#include "../common/triggers/subs.qh" +#include "../common/mapobjects/subs.qh" +#include -#include "../common/triggers/func/breakable.qh" +#include "../common/mapobjects/func/breakable.qh" #include "../lib/csqcmodel/sv_model.qh" @@ -151,12 +153,12 @@ float CheatImpulse(entity this, int imp) this.personal.origin = this.origin; this.personal.v_angle = this.v_angle; this.personal.velocity = this.velocity; - this.personal.ammo_rockets = this.ammo_rockets; - this.personal.ammo_nails = this.ammo_nails; - this.personal.ammo_cells = this.ammo_cells; - this.personal.ammo_plasma = this.ammo_plasma; - this.personal.ammo_shells = this.ammo_shells; - this.personal.ammo_fuel = this.ammo_fuel; + SetResourceAmount(this.personal, RESOURCE_ROCKETS, GetResourceAmount(this, RESOURCE_ROCKETS)); + SetResourceAmount(this.personal, RESOURCE_BULLETS, GetResourceAmount(this, RESOURCE_BULLETS)); + SetResourceAmount(this.personal, RESOURCE_CELLS, GetResourceAmount(this, RESOURCE_CELLS)); + SetResourceAmount(this.personal, RESOURCE_PLASMA, GetResourceAmount(this, RESOURCE_PLASMA)); + SetResourceAmount(this.personal, RESOURCE_SHELLS, GetResourceAmount(this, RESOURCE_SHELLS)); + SetResourceAmount(this.personal, RESOURCE_FUEL, GetResourceAmount(this, RESOURCE_FUEL)); this.personal.health = max(1, this.health); this.personal.armorvalue = this.armorvalue; STAT(WEAPONS, this.personal) = STAT(WEAPONS, this); @@ -210,12 +212,12 @@ float CheatImpulse(entity this, int imp) MUTATOR_CALLHOOK(AbortSpeedrun, this); } - this.ammo_rockets = this.personal.ammo_rockets; - this.ammo_nails = this.personal.ammo_nails; - this.ammo_cells = this.personal.ammo_cells; - this.ammo_plasma = this.personal.ammo_plasma; - this.ammo_shells = this.personal.ammo_shells; - this.ammo_fuel = this.personal.ammo_fuel; + SetResourceAmount(this, RESOURCE_ROCKETS, GetResourceAmount(this.personal, RESOURCE_ROCKETS)); + SetResourceAmount(this, RESOURCE_BULLETS, GetResourceAmount(this.personal, RESOURCE_BULLETS)); + SetResourceAmount(this, RESOURCE_CELLS, GetResourceAmount(this.personal, RESOURCE_CELLS)); + SetResourceAmount(this, RESOURCE_PLASMA, GetResourceAmount(this.personal, RESOURCE_PLASMA)); + SetResourceAmount(this, RESOURCE_SHELLS, GetResourceAmount(this.personal, RESOURCE_SHELLS)); + SetResourceAmount(this, RESOURCE_FUEL, GetResourceAmount(this.personal, RESOURCE_FUEL)); this.health = this.personal.health; this.armorvalue = this.personal.armorvalue; STAT(WEAPONS, this) = STAT(WEAPONS, this.personal); diff --git a/qcsrc/server/client.qc b/qcsrc/server/client.qc index 5d1734c63..7cd2a9932 100644 --- a/qcsrc/server/client.qc +++ b/qcsrc/server/client.qc @@ -33,9 +33,9 @@ #include -#include "../common/triggers/func/conveyor.qh" -#include "../common/triggers/teleporters.qh" -#include "../common/triggers/target/spawnpoint.qh" +#include "../common/mapobjects/func/conveyor.qh" +#include "../common/mapobjects/teleporters.qh" +#include "../common/mapobjects/target/spawnpoint.qh" #include "../common/vehicles/all.qh" @@ -50,10 +50,11 @@ #include "../common/mutators/mutator/waypoints/all.qh" #include "../common/mutators/mutator/instagib/sv_instagib.qh" +#include -#include "../common/triggers/subs.qh" -#include "../common/triggers/triggers.qh" -#include "../common/triggers/trigger/secret.qh" +#include "../common/mapobjects/subs.qh" +#include "../common/mapobjects/triggers.qh" +#include "../common/mapobjects/trigger/secret.qh" #include "../common/minigames/sv_minigames.qh" @@ -556,22 +557,22 @@ void PutPlayerInServer(entity this) this.effects = EF_TELEPORT_BIT | EF_RESTARTANIM_BIT; if (warmup_stage) { - this.ammo_shells = warmup_start_ammo_shells; - this.ammo_nails = warmup_start_ammo_nails; - this.ammo_rockets = warmup_start_ammo_rockets; - this.ammo_cells = warmup_start_ammo_cells; - this.ammo_plasma = warmup_start_ammo_plasma; - this.ammo_fuel = warmup_start_ammo_fuel; + SetResourceAmount(this, RESOURCE_SHELLS, warmup_start_ammo_shells); + SetResourceAmount(this, RESOURCE_BULLETS, warmup_start_ammo_nails); + SetResourceAmount(this, RESOURCE_ROCKETS, warmup_start_ammo_rockets); + SetResourceAmount(this, RESOURCE_CELLS, warmup_start_ammo_cells); + SetResourceAmount(this, RESOURCE_PLASMA, warmup_start_ammo_plasma); + SetResourceAmount(this, RESOURCE_FUEL, warmup_start_ammo_fuel); this.health = warmup_start_health; this.armorvalue = warmup_start_armorvalue; STAT(WEAPONS, this) = WARMUP_START_WEAPONS; } else { - this.ammo_shells = start_ammo_shells; - this.ammo_nails = start_ammo_nails; - this.ammo_rockets = start_ammo_rockets; - this.ammo_cells = start_ammo_cells; - this.ammo_plasma = start_ammo_plasma; - this.ammo_fuel = start_ammo_fuel; + SetResourceAmount(this, RESOURCE_SHELLS, start_ammo_shells); + SetResourceAmount(this, RESOURCE_BULLETS, start_ammo_nails); + SetResourceAmount(this, RESOURCE_ROCKETS, start_ammo_rockets); + SetResourceAmount(this, RESOURCE_CELLS, start_ammo_cells); + SetResourceAmount(this, RESOURCE_PLASMA, start_ammo_plasma); + SetResourceAmount(this, RESOURCE_FUEL, start_ammo_fuel); this.health = start_health; this.armorvalue = start_armorvalue; STAT(WEAPONS, this) = start_weapons; @@ -1824,7 +1825,7 @@ void SpectateCopy(entity this, entity spectatee) PS(this) = PS(spectatee); this.armortype = spectatee.armortype; this.armorvalue = spectatee.armorvalue; - this.ammo_cells = spectatee.ammo_cells; + this.ammo_cells = spectatee.ammo_cells; // TODO: these will be a part of inventory, so no need to worry about setting them later! this.ammo_plasma = spectatee.ammo_plasma; this.ammo_shells = spectatee.ammo_shells; this.ammo_nails = spectatee.ammo_nails; diff --git a/qcsrc/server/command/cmd.qc b/qcsrc/server/command/cmd.qc index e0c72dbc2..8111a64ef 100644 --- a/qcsrc/server/command/cmd.qc +++ b/qcsrc/server/command/cmd.qc @@ -16,7 +16,8 @@ #include "../scores.qh" #include "../teamplay.qh" -#include "../mutators/_mod.qh" +#include +#include #ifdef SVQC #include @@ -30,7 +31,7 @@ #include #include #include -#include +#include #include diff --git a/qcsrc/server/command/radarmap.qc b/qcsrc/server/command/radarmap.qc index 6c31af72c..87bcef82f 100644 --- a/qcsrc/server/command/radarmap.qc +++ b/qcsrc/server/command/radarmap.qc @@ -4,7 +4,6 @@ #include #include "../g_world.qh" -#include "../g_subs.qh" #include diff --git a/qcsrc/server/command/sv_cmd.qc b/qcsrc/server/command/sv_cmd.qc index 6de4507b1..0471cff07 100644 --- a/qcsrc/server/command/sv_cmd.qc +++ b/qcsrc/server/command/sv_cmd.qc @@ -20,7 +20,8 @@ #include "../bot/api.qh" -#include "../mutators/_mod.qh" +#include +#include #include #include diff --git a/qcsrc/server/command/vote.qc b/qcsrc/server/command/vote.qc index 638dbb156..5f034f12f 100644 --- a/qcsrc/server/command/vote.qc +++ b/qcsrc/server/command/vote.qc @@ -14,7 +14,8 @@ #include "../round_handler.qh" #include "../scores.qh" -#include "../mutators/_mod.qh" +#include +#include #include #include diff --git a/qcsrc/server/compat/quake3.qc b/qcsrc/server/compat/quake3.qc index 4f973bb9e..de069be87 100644 --- a/qcsrc/server/compat/quake3.qc +++ b/qcsrc/server/compat/quake3.qc @@ -4,6 +4,7 @@ #include #include #include +#include #include spawnfunc(target_items); diff --git a/qcsrc/server/constants.qh b/qcsrc/server/constants.qh index 86ed86da1..f98d586fb 100644 --- a/qcsrc/server/constants.qh +++ b/qcsrc/server/constants.qh @@ -17,5 +17,3 @@ const int RESPAWN_DENY = 4; #define EFMASK_CHEAP (EF_ADDITIVE | EF_DOUBLESIDED | EF_FULLBRIGHT | EF_NODEPTHTEST | EF_NODRAW | EF_NOGUNBOB | EF_NOSHADOW | EF_LOWPRECISION | EF_SELECTABLE | EF_TELEPORT_BIT) const int NUM_PLAYERSKINS_TEAMPLAY = 3; - -const int ASSAULT_VALUE_INACTIVE = 1000; diff --git a/qcsrc/server/defs.qh b/qcsrc/server/defs.qh index 5230bd8ea..1db5dd0c5 100644 --- a/qcsrc/server/defs.qh +++ b/qcsrc/server/defs.qh @@ -277,9 +277,6 @@ void W_Porto_Remove (entity p); // Nexball float g_nexball_meter_period; -void SUB_DontUseTargets(entity this, entity actor, entity trigger); -void SUB_UseTargets(entity this, entity actor, entity trigger); - .void(entity this) reset; // if set, an entity is reset using this .void(entity this) reset2; // if set, an entity is reset using this (after calling ALL the reset functions for other entities) diff --git a/qcsrc/server/g_damage.qc b/qcsrc/server/g_damage.qc index dcbc20342..cefeb316a 100644 --- a/qcsrc/server/g_damage.qc +++ b/qcsrc/server/g_damage.qc @@ -3,7 +3,7 @@ #include #include "bot/api.qh" #include "g_hook.qh" -#include "mutators/_mod.qh" +#include #include "scores.qh" #include "spawnpoints.qh" #include "../common/state.qh" @@ -24,6 +24,7 @@ #include "../common/playerstats.qh" #include "../common/teams.qh" #include "../common/util.qh" +#include #include #include "../lib/csqcmodel/sv_model.qh" #include "../lib/warpzone/common.qh" @@ -61,51 +62,8 @@ void GiveFrags (entity attacker, entity targ, float f, int deathtype, .entity we GameRules_scoring_add(targ, DEATHS, 1); - if(targ != attacker) // not for suicides - if(g_weaponarena_random) - { - // after a frag, exchange the current weapon (or the culprit, if detectable) by a new random weapon - Weapon culprit = DEATH_WEAPONOF(deathtype); - if(!culprit) culprit = attacker.(weaponentity).m_weapon; - else if(!(STAT(WEAPONS, attacker) & (culprit.m_wepset))) culprit = attacker.(weaponentity).m_weapon; - - if(g_weaponarena_random_with_blaster && culprit == WEP_BLASTER) // WEAPONTODO: Shouldn't this be in a mutator? - { - // no exchange - } - else - { - if(!GiveFrags_randomweapons) - { - GiveFrags_randomweapons = new(GiveFrags_randomweapons); - } - - if(warmup_stage) - STAT(WEAPONS, GiveFrags_randomweapons) = WARMUP_START_WEAPONS; - else - STAT(WEAPONS, GiveFrags_randomweapons) = start_weapons; - - // all others (including the culprit): remove - STAT(WEAPONS, GiveFrags_randomweapons) &= ~STAT(WEAPONS, attacker); - STAT(WEAPONS, GiveFrags_randomweapons) &= ~(culprit.m_wepset); - - // among the remaining ones, choose one by random - STAT(WEAPONS, GiveFrags_randomweapons) = W_RandomWeapons(GiveFrags_randomweapons, STAT(WEAPONS, GiveFrags_randomweapons), 1); - - if(STAT(WEAPONS, GiveFrags_randomweapons)) - { - STAT(WEAPONS, attacker) |= STAT(WEAPONS, GiveFrags_randomweapons); - STAT(WEAPONS, attacker) &= ~(culprit.m_wepset); - } - } - - // after a frag, choose another random weapon set - if (!(STAT(WEAPONS, attacker) & WepSet_FromWeapon(attacker.(weaponentity).m_weapon))) - W_SwitchWeapon_Force(attacker, w_getbestweapon(attacker, weaponentity), weaponentity); - } - // FIXME fix the mess this is (we have REAL points now!) - if(MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f)) + if(MUTATOR_CALLHOOK(GiveFragsForKill, attacker, targ, f, deathtype, attacker.(weaponentity))) f = M_ARGV(2, float); attacker.totalfrags += f; diff --git a/qcsrc/server/g_damage.qh b/qcsrc/server/g_damage.qh index 4f68c2ea0..9ab817853 100644 --- a/qcsrc/server/g_damage.qh +++ b/qcsrc/server/g_damage.qh @@ -19,7 +19,7 @@ #include "defs.qh" #include #include - #include "mutators/_mod.qh" + #include #include #include #include @@ -58,7 +58,6 @@ void UpdateFrags(entity player, int f); // NOTE: f=0 means still count as a (positive) kill, but count no frags for it void W_SwitchWeapon_Force(Player this, Weapon w, .entity weaponentity); -entity GiveFrags_randomweapons; void GiveFrags (entity attacker, entity targ, float f, int deathtype, .entity weaponentity); string AppendItemcodes(string s, entity player); diff --git a/qcsrc/server/g_lights.qc b/qcsrc/server/g_lights.qc deleted file mode 100644 index 852f1efc0..000000000 --- a/qcsrc/server/g_lights.qc +++ /dev/null @@ -1,135 +0,0 @@ -#include "g_lights.qh" - -#include -#include - -void train_next(entity this); - -const float LOOP = 1; - -.float speed; - -const float DNOSHADOW = 2; -const float DFOLLOW = 4; -.float light_lev; -.float lefty; -.vector color; -.string dtagname; - -/*QUAKED dynlight (0 1 0) (-8 -8 -8) (8 8 8) START_OFF NOSHADOW FOLLOW -Dynamic spawnfunc_light. -Can do one of these things: sit still and be just a silly spawnfunc_light, travel along a path, follow an entity around, attach to a tag on an entity. -It can spin around it's own axis in all the above cases. -If targeted, it will toggle between on or off. -keys: -"light_lev" spawnfunc_light radius, default 200 -"color" spawnfunc_light color in rgb and brightness, 1 1 1 produces bright white, up to 255 255 255 (nuclear blast), recommended values up to 1 1 1, default 1 1 1 -"style" lightstyle, same as for static lights -"angles" initial orientation -"avelocity" a vector value, the direction and speed it rotates in -"skin" cubemap number, must be 16 or above -"dtagname" will attach to this tag on the entity which "targetname" matches "target". If the "target" is either not an md3 model or is missing tags, it will attach to the targets origin. Note that the "target" must be visible to the spawnfunc_light -"targetname" will toggle on and off when triggered -"target" if issued with a target, preferrably spawnfunc_path_corner, it will move along the path. If also issued with the FOLLOW spawnflag, then this is the entity it will follow. If issued with the "tagname" key it will attach it to this targets tag called "tagname", does not work together with FOLLOW or path movement -"speed" the speed it will travel along the path, default 100 -flags: -"START_OFF" spawnfunc_light will be in off state until targeted -"NOSHADOW" will not cast shadows in realtime lighting mode -"FOLLOW" will follow the entity which "targetname" matches "target" -*/ -void dynlight_think(entity this) -{ - if(!this.owner) - delete(this); - - this.nextthink = time + 0.1; -} -void dynlight_find_aiment(entity this) -{ - entity targ; - if (!this.target) - objerror (this, "dynlight: no target to follow"); - - targ = find(NULL, targetname, this.target); - set_movetype(this, MOVETYPE_FOLLOW); - this.aiment = targ; - this.owner = targ; - this.punchangle = targ.angles; - this.view_ofs = this.origin - targ.origin; - this.v_angle = this.angles - targ.angles; - setthink(this, dynlight_think); - this.nextthink = time + 0.1; -} -void dynlight_find_path(entity this) -{ - entity targ; - if (!this.target) - objerror (this, "dynlight: no target to follow"); - - targ = find(NULL, targetname, this.target); - this.target = targ.target; - setorigin(this, targ.origin); - setthink(this, train_next); - this.nextthink = time + 0.1; -} -void dynlight_find_target(entity this) -{ - entity targ; - if (!this.target) - objerror (this, "dynlight: no target to follow"); - - targ = find(NULL, targetname, this.target); - setattachment(this, targ, this.dtagname); - this.owner = targ; - setthink(this, dynlight_think); - this.nextthink = time + 0.1; -} -void dynlight_use(entity this, entity actor, entity trigger) -{ - if (this.light_lev == 0) - this.light_lev = this.lefty; - else - this.light_lev = 0; -} -spawnfunc(dynlight) -{ - if (!this.light_lev) - this.light_lev = 200; - if (!this.color) - this.color = '1 1 1'; - this.lefty = this.light_lev; - this.use = dynlight_use; - setsize (this, '0 0 0', '0 0 0'); - setorigin(this, this.origin); - //this.pflags = PFLAGS_FULLDYNAMIC; - this.solid = SOLID_NOT; - //this.blocked = func_null; - //if (this.spawnflags & DNOSHADOW) - // this.pflags = this.pflags + PFLAGS_NOSHADOW; - //if (this.spawnflags & START_OFF) - // this.light_lev = 0; - -//tag attaching - if (this.dtagname) - { - InitializeEntity(this, dynlight_find_target, INITPRIO_FINDTARGET); - return; - } - -// entity following - if (this.spawnflags & DFOLLOW) - { - InitializeEntity(this, dynlight_find_aiment, INITPRIO_FINDTARGET); - return; - } -// path following - if (this.target) -// if (!(this.spawnflags & DFOLLOW)) - { - set_movetype(this, MOVETYPE_NOCLIP); - if (!this.speed) - this.speed = 100; - InitializeEntity(this, dynlight_find_path, INITPRIO_FINDTARGET); - return; - } -} diff --git a/qcsrc/server/g_lights.qh b/qcsrc/server/g_lights.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/server/g_lights.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/server/g_models.qc b/qcsrc/server/g_models.qc deleted file mode 100644 index c8f85247c..000000000 --- a/qcsrc/server/g_models.qc +++ /dev/null @@ -1,194 +0,0 @@ -#include "g_models.qh" - -#include -#include -#include "g_subs.qh" -#include -#include "../common/triggers/subs.qh" -#include "../common/triggers/triggers.qh" - -entityclass(BGMScript); -classfield(BGMScript) .string bgmscript; -classfield(BGMScript) .float bgmscriptattack; -classfield(BGMScript) .float bgmscriptdecay; -classfield(BGMScript) .float bgmscriptsustain; -classfield(BGMScript) .float bgmscriptrelease; - -#include "../common/constants.qh" -#include "../lib/csqcmodel/sv_model.qh" - -.float modelscale; - -void g_model_setcolormaptoactivator(entity this, entity actor, entity trigger) -{ - if(teamplay) - { - if(actor.team) - this.colormap = (actor.team - 1) * 0x11; - else - this.colormap = 0x00; - } - else - this.colormap = floor(random() * 256); - this.colormap |= BIT(10); // RENDER_COLORMAPPED -} - -void g_clientmodel_setcolormaptoactivator(entity this, entity actor, entity trigger) -{ - g_model_setcolormaptoactivator(this, actor, trigger); - this.SendFlags |= (BIT(3) | BIT(0)); -} - -void g_clientmodel_use(entity this, entity actor, entity trigger) -{ - if (this.antiwall_flag == 1) - { - this.inactive = 1; - this.solid = SOLID_NOT; - } - else if (this.antiwall_flag == 2) - { - this.inactive = 0; - this.solid = this.default_solid; - } - g_clientmodel_setcolormaptoactivator(this, actor, trigger); -} - -void g_model_dropbyspawnflags(entity this) -{ - if((this.spawnflags & 3) == 1) // ALIGN_ORIGIN - { - traceline(this.origin, this.origin - '0 0 4096', MOVE_NOMONSTERS, this); - setorigin(this, trace_endpos); - } - else if((this.spawnflags & 3) == 2) // ALIGN_BOTTOM - { - tracebox(this.origin, this.mins, this.maxs, this.origin - '0 0 4096', MOVE_NOMONSTERS, this); - setorigin(this, trace_endpos); - } - else if((this.spawnflags & 3) == 3) // ALIGN_ORIGIN | ALIGN_BOTTOM - { - traceline(this.origin, this.origin - '0 0 4096', MOVE_NOMONSTERS, this); - setorigin(this, trace_endpos - '0 0 1' * this.mins.z); - } -} - -void g_clientmodel_dropbyspawnflags(entity this) -{ - vector o0; - o0 = this.origin; - g_model_dropbyspawnflags(this); - if(this.origin != o0) - this.SendFlags |= 2; -} - -bool g_clientmodel_genericsendentity(entity this, entity to, int sf) -{ - sf = sf & 0x0F; - if(this.angles != '0 0 0') - sf |= 0x10; - if(this.mins != '0 0 0' || this.maxs != '0 0 0') - sf |= 0x20; - if(this.colormap != 0) - sf |= 0x40; - if(this.lodmodelindex1) - sf |= 0x80; - - WriteHeader(MSG_ENTITY, ENT_CLIENT_WALL); - WriteByte(MSG_ENTITY, sf); - - if(sf & BIT(0)) - { - if(sf & 0x40) - WriteShort(MSG_ENTITY, this.colormap); - WriteByte(MSG_ENTITY, this.skin); - } - - if(sf & BIT(1)) - { - WriteVector(MSG_ENTITY, this.origin); - } - - if(sf & BIT(2)) - { - if(sf & 0x10) - { - WriteAngle(MSG_ENTITY, this.angles.x); - WriteAngle(MSG_ENTITY, this.angles.y); - WriteAngle(MSG_ENTITY, this.angles.z); - } - } - - if(sf & BIT(3)) - { - if(sf & 0x80) - { - WriteShort(MSG_ENTITY, this.lodmodelindex0); - WriteShort(MSG_ENTITY, bound(0, this.loddistance1, 65535)); - WriteShort(MSG_ENTITY, this.lodmodelindex1); - WriteShort(MSG_ENTITY, bound(0, this.loddistance2, 65535)); - WriteShort(MSG_ENTITY, this.lodmodelindex2); - } - else - WriteShort(MSG_ENTITY, this.modelindex); - WriteByte(MSG_ENTITY, this.solid); - WriteShort(MSG_ENTITY, floor(this.scale * 256)); - if(sf & 0x20) - { - WriteVector(MSG_ENTITY, this.mins); - WriteVector(MSG_ENTITY, this.maxs); - } - WriteString(MSG_ENTITY, this.bgmscript); - if(this.bgmscript != "") - { - WriteByte(MSG_ENTITY, floor(this.bgmscriptattack * 64)); - WriteByte(MSG_ENTITY, floor(this.bgmscriptdecay * 64)); - WriteByte(MSG_ENTITY, floor(this.bgmscriptsustain * 255)); - WriteByte(MSG_ENTITY, floor(this.bgmscriptrelease * 64)); - WriteVector(MSG_ENTITY, this.movedir); - WriteByte(MSG_ENTITY, floor(this.lip * 255)); - } - WriteByte(MSG_ENTITY, this.fade_start); - WriteByte(MSG_ENTITY, this.fade_end); - WriteByte(MSG_ENTITY, this.alpha_max); - WriteByte(MSG_ENTITY, this.alpha_min); - WriteByte(MSG_ENTITY, this.inactive); - WriteShort(MSG_ENTITY, this.fade_vertical_offset); - } - - return true; -} - - -#define G_MODEL_INIT(ent,sol) \ - if(ent.geomtype) if(autocvar_physics_ode && checkextension("DP_PHYSICS_ODE")) set_movetype(ent, MOVETYPE_PHYSICS); \ - if(!ent.scale) ent.scale = ent.modelscale; \ - SetBrushEntityModel(ent); \ - ent.use = g_model_setcolormaptoactivator; \ - InitializeEntity(ent, g_model_dropbyspawnflags, INITPRIO_DROPTOFLOOR); \ - if(!ent.solid) ent.solid = (sol); else if(ent.solid < 0) ent.solid = SOLID_NOT; - -#define G_CLIENTMODEL_INIT(ent,sol) \ - if(ent.geomtype) if(autocvar_physics_ode && checkextension("DP_PHYSICS_ODE")) set_movetype(ent, MOVETYPE_PHYSICS); \ - if(!ent.scale) ent.scale = ent.modelscale; \ - SetBrushEntityModel(ent); \ - ent.use = g_clientmodel_use; \ - InitializeEntity(ent, g_clientmodel_dropbyspawnflags, INITPRIO_DROPTOFLOOR); \ - if(!ent.solid) ent.solid = (sol); else if(ent.solid < 0) ent.solid = SOLID_NOT; \ - if(!ent.bgmscriptsustain) ent.bgmscriptsustain = 1; else if(ent.bgmscriptsustain < 0) ent.bgmscriptsustain = 0; \ - Net_LinkEntity(ent, true, 0, g_clientmodel_genericsendentity); \ - ent.default_solid = sol; - -// non-solid model entities: -spawnfunc(misc_gamemodel) { this.angles_x = -this.angles.x; G_MODEL_INIT (this, SOLID_NOT) } // model entity -spawnfunc(misc_clientmodel) { this.angles_x = -this.angles.x; G_CLIENTMODEL_INIT(this, SOLID_NOT) } // model entity -spawnfunc(misc_models) { this.angles_x = -this.angles.x; G_MODEL_INIT (this, SOLID_NOT) } // DEPRECATED old compat entity with confusing name, do not use - -// non-solid brush entities: -spawnfunc(func_illusionary) { G_MODEL_INIT (this, SOLID_NOT) } // Q1 name (WARNING: MISPREDICTED) -spawnfunc(func_clientillusionary) { G_CLIENTMODEL_INIT(this, SOLID_NOT) } // brush entity -spawnfunc(func_static) { G_MODEL_INIT (this, SOLID_NOT) } // DEPRECATED old alias name from some other game - -// solid brush entities -spawnfunc(func_wall) { G_MODEL_INIT (this, SOLID_BSP) } // Q1 name -spawnfunc(func_clientwall) { G_CLIENTMODEL_INIT(this, SOLID_BSP) } // brush entity (WARNING: MISPREDICTED) diff --git a/qcsrc/server/g_models.qh b/qcsrc/server/g_models.qh deleted file mode 100644 index 6f70f09be..000000000 --- a/qcsrc/server/g_models.qh +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/qcsrc/server/g_subs.qc b/qcsrc/server/g_subs.qc deleted file mode 100644 index d9372e0aa..000000000 --- a/qcsrc/server/g_subs.qc +++ /dev/null @@ -1,439 +0,0 @@ -#include "g_subs.qh" - -#include -#include -#include "antilag.qh" -#include "command/common.qh" -#include "../common/state.qh" -#include "../lib/warpzone/common.qh" -#include "../common/triggers/subs.qh" - -spawnfunc(info_null) -{ - delete(this); - // if anything breaks, tell the mapper to fix his map! info_null is meant to remove itself immediately. -} - -/* -================== -main - -unused but required by the engine -================== -*/ -void main () -{ - -} - -// Misc - -/* -================== -traceline_antilag - -A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack -Additionally it moves players back into the past before the trace and restores them afterward. -================== -*/ -void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz) -{ - // check whether antilagged traces are enabled - if (lag < 0.001) - lag = 0; - if (!IS_REAL_CLIENT(forent)) - lag = 0; // only antilag for clients - - // change shooter to SOLID_BBOX so the shot can hit corpses - int oldsolid = source.dphitcontentsmask; - if(source) - source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE; - - if (lag) - antilag_takeback_all(forent, lag); - - // do the trace - if(wz) - WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent); - else - tracebox (v1, mi, ma, v2, nomonst, forent); - - // restore players to current positions - if (lag) - antilag_restore_all(forent); - - // restore shooter solid type - if(source) - source.dphitcontentsmask = oldsolid; -} -void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag) -{ - tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, false); -} -void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag) -{ - bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false); - if (autocvar_g_antilag != 2 || noantilag) - lag = 0; - traceline_antilag_force(source, v1, v2, nomonst, forent, lag); -} -void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag) -{ - bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false); - if (autocvar_g_antilag != 2 || noantilag) - lag = 0; - tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, false); -} -void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag) -{ - tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, true); -} -void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag) -{ - bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false); - if (autocvar_g_antilag != 2 || noantilag) - lag = 0; - WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag); -} -void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag) -{ - bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false); - if (autocvar_g_antilag != 2 || noantilag) - lag = 0; - tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, true); -} - -float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity) // returns the number of traces done, for benchmarking -{ - vector pos, dir, t; - float nudge; - entity stopentity; - - //nudge = 2 * cvar("collision_impactnudge"); // why not? - nudge = 0.5; - - dir = normalize(v2 - v1); - - pos = v1 + dir * nudge; - - float c; - c = 0; - - for (;;) - { - if(pos * dir >= v2 * dir) - { - // went too far - trace_fraction = 1; - trace_endpos = v2; - return c; - } - - tracebox(pos, mi, ma, v2, nomonsters, forent); - ++c; - - if(c == 50) - { - LOG_TRACE("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2)); - LOG_TRACE(" Nudging gets us nowhere at ", vtos(pos)); - LOG_TRACE(" trace_endpos is ", vtos(trace_endpos)); - LOG_TRACE(" trace distance is ", ftos(vlen(pos - trace_endpos))); - } - - stopentity = trace_ent; - - if(trace_startsolid) - { - // we started inside solid. - // then trace from endpos to pos - t = trace_endpos; - tracebox(t, mi, ma, pos, nomonsters, forent); - ++c; - if(trace_startsolid) - { - // t is still inside solid? bad - // force advance, then, and retry - pos = t + dir * nudge; - - // but if we hit an entity, stop RIGHT before it - if(stopatentity && stopentity && stopentity != ignorestopatentity) - { - trace_ent = stopentity; - trace_endpos = t; - trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir); - return c; - } - } - else - { - // we actually LEFT solid! - trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir); - return c; - } - } - else - { - // pos is outside solid?!? but why?!? never mind, just return it. - trace_endpos = pos; - trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir); - return c; - } - } -} - -void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity) -{ - tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity, ignorestopatentity); -} - -/* -================== -findbetterlocation - -Returns a point at least 12 units away from walls -(useful for explosion animations, although the blast is performed where it really happened) -Ripped from DPMod -================== -*/ -vector findbetterlocation (vector org, float mindist) -{ - vector vec = mindist * '1 0 0'; - int c = 0; - while (c < 6) - { - traceline (org, org + vec, true, NULL); - vec = vec * -1; - if (trace_fraction < 1) - { - vector loc = trace_endpos; - traceline (loc, loc + vec, true, NULL); - if (trace_fraction >= 1) - org = loc + vec; - } - if (c & 1) - { - float h = vec.y; - vec.y = vec.x; - vec.x = vec.z; - vec.z = h; - } - c = c + 1; - } - - return org; -} - -bool LOD_customize(entity this, entity client) -{ - if(autocvar_loddebug) - { - int d = autocvar_loddebug; - if(d == 1) - this.modelindex = this.lodmodelindex0; - else if(d == 2 || !this.lodmodelindex2) - this.modelindex = this.lodmodelindex1; - else // if(d == 3) - this.modelindex = this.lodmodelindex2; - return true; - } - - // TODO csqc network this so it only gets sent once - vector near_point = NearestPointOnBox(this, client.origin); - if(vdist(near_point - client.origin, <, this.loddistance1)) - this.modelindex = this.lodmodelindex0; - else if(!this.lodmodelindex2 || vdist(near_point - client.origin, <, this.loddistance2)) - this.modelindex = this.lodmodelindex1; - else - this.modelindex = this.lodmodelindex2; - - return true; -} - -void LOD_uncustomize(entity this) -{ - this.modelindex = this.lodmodelindex0; -} - -void LODmodel_attach(entity this) -{ - entity e; - - if(!this.loddistance1) - this.loddistance1 = 1000; - if(!this.loddistance2) - this.loddistance2 = 2000; - this.lodmodelindex0 = this.modelindex; - - if(this.lodtarget1 != "") - { - e = find(NULL, targetname, this.lodtarget1); - if(e) - { - this.lodmodel1 = e.model; - delete(e); - } - } - if(this.lodtarget2 != "") - { - e = find(NULL, targetname, this.lodtarget2); - if(e) - { - this.lodmodel2 = e.model; - delete(e); - } - } - - if(autocvar_loddebug < 0) - { - this.lodmodel1 = this.lodmodel2 = ""; // don't even initialize - } - - if(this.lodmodel1 != "") - { - vector mi, ma; - mi = this.mins; - ma = this.maxs; - - precache_model(this.lodmodel1); - _setmodel(this, this.lodmodel1); - this.lodmodelindex1 = this.modelindex; - - if(this.lodmodel2 != "") - { - precache_model(this.lodmodel2); - _setmodel(this, this.lodmodel2); - this.lodmodelindex2 = this.modelindex; - } - - this.modelindex = this.lodmodelindex0; - setsize(this, mi, ma); - } - - if(this.lodmodelindex1) - if (!getSendEntity(this)) - SetCustomizer(this, LOD_customize, LOD_uncustomize); -} - -void ApplyMinMaxScaleAngles(entity e) -{ - if(e.angles.x != 0 || e.angles.z != 0 || e.avelocity.x != 0 || e.avelocity.z != 0) // "weird" rotation - { - e.maxs = '1 1 1' * vlen( - '1 0 0' * max(-e.mins.x, e.maxs.x) + - '0 1 0' * max(-e.mins.y, e.maxs.y) + - '0 0 1' * max(-e.mins.z, e.maxs.z) - ); - e.mins = -e.maxs; - } - else if(e.angles.y != 0 || e.avelocity.y != 0) // yaw only is a bit better - { - e.maxs_x = vlen( - '1 0 0' * max(-e.mins.x, e.maxs.x) + - '0 1 0' * max(-e.mins.y, e.maxs.y) - ); - e.maxs_y = e.maxs.x; - e.mins_x = -e.maxs.x; - e.mins_y = -e.maxs.x; - } - if(e.scale) - setsize(e, e.mins * e.scale, e.maxs * e.scale); - else - setsize(e, e.mins, e.maxs); -} - -void SetBrushEntityModel(entity this) -{ - if(this.model != "") - { - precache_model(this.model); - if(this.mins != '0 0 0' || this.maxs != '0 0 0') - { - vector mi = this.mins; - vector ma = this.maxs; - _setmodel(this, this.model); // no precision needed - setsize(this, mi, ma); - } - else - _setmodel(this, this.model); // no precision needed - InitializeEntity(this, LODmodel_attach, INITPRIO_FINDTARGET); - } - setorigin(this, this.origin); - ApplyMinMaxScaleAngles(this); -} - -void SetBrushEntityModelNoLOD(entity this) -{ - if(this.model != "") - { - precache_model(this.model); - if(this.mins != '0 0 0' || this.maxs != '0 0 0') - { - vector mi = this.mins; - vector ma = this.maxs; - _setmodel(this, this.model); // no precision needed - setsize(this, mi, ma); - } - else - _setmodel(this, this.model); // no precision needed - } - setorigin(this, this.origin); - ApplyMinMaxScaleAngles(this); -} - -/* -================ -InitTrigger -================ -*/ - -void SetMovedir(entity this) -{ - if(this.movedir != '0 0 0') - this.movedir = normalize(this.movedir); - else - { - makevectors(this.angles); - this.movedir = v_forward; - } - - this.angles = '0 0 0'; -} - -void InitTrigger(entity this) -{ -// trigger angles are used for one-way touches. An angle of 0 is assumed -// to mean no restrictions, so use a yaw of 360 instead. - SetMovedir(this); - this.solid = SOLID_TRIGGER; - SetBrushEntityModel(this); - set_movetype(this, MOVETYPE_NONE); - this.modelindex = 0; - this.model = ""; -} - -void InitSolidBSPTrigger(entity this) -{ -// trigger angles are used for one-way touches. An angle of 0 is assumed -// to mean no restrictions, so use a yaw of 360 instead. - SetMovedir(this); - this.solid = SOLID_BSP; - SetBrushEntityModel(this); - set_movetype(this, MOVETYPE_NONE); // why was this PUSH? -div0 -// this.modelindex = 0; - this.model = ""; -} - -bool InitMovingBrushTrigger(entity this) -{ -// trigger angles are used for one-way touches. An angle of 0 is assumed -// to mean no restrictions, so use a yaw of 360 instead. - this.solid = SOLID_BSP; - SetBrushEntityModel(this); - set_movetype(this, MOVETYPE_PUSH); - if(this.modelindex == 0) - { - objerror(this, "InitMovingBrushTrigger: no brushes found!"); - return false; - } - return true; -} diff --git a/qcsrc/server/g_subs.qh b/qcsrc/server/g_subs.qh deleted file mode 100644 index 1f5537cea..000000000 --- a/qcsrc/server/g_subs.qh +++ /dev/null @@ -1,165 +0,0 @@ -#pragma once - -void SUB_NullThink(entity this); - -void SUB_CalcMoveDone(entity this); -void SUB_CalcAngleMoveDone(entity this); - -spawnfunc(info_null); - -/* -================== -SUB_Friction - -Applies some friction to this -================== -*/ -.float friction; -void SUB_Friction (entity this); - -/* -================== -SUB_VanishOrRemove - -Makes client invisible or removes non-client -================== -*/ -void SUB_VanishOrRemove (entity ent); - -void SUB_SetFade_Think (entity this); - -/* -================== -SUB_SetFade - -Fade 'ent' out when time >= 'when' -================== -*/ -void SUB_SetFade (entity ent, float when, float fadetime); - -/* -============= -SUB_CalcMove - -calculate this.velocity and this.nextthink to reach dest from -this.origin traveling at speed -=============== -*/ -void SUB_CalcMoveDone(entity this); - -.float platmovetype_turn; -void SUB_CalcMove_controller_think (entity this); - -void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest); - -void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest); - -void SUB_CalcMove_Bezier (entity this, vector tcontrol, vector tdest, float tspeedtype, float tspeed, void(entity this) func); - -void SUB_CalcMove (entity this, vector tdest, float tspeedtype, float tspeed, void(entity this) func); - -void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void(entity this) func); - -/* -============= -SUB_CalcAngleMove - -calculate this.avelocity and this.nextthink to reach destangle from -this.angles rotating - -The calling function should make sure this.think is valid -=============== -*/ -void SUB_CalcAngleMoveDone (entity this); - -// FIXME: I fixed this function only for rotation around the main axes -void SUB_CalcAngleMove (entity this, vector destangle, float tspeedtype, float tspeed, void(entity this) func); - -void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void(entity this) func); - -/* -================== -main - -unused but required by the engine -================== -*/ -void main (); - -// Misc - -/* -================== -traceline_antilag - -A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack -Additionally it moves players back into the past before the trace and restores them afterward. -================== -*/ -void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz); -void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag); -void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag); -void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag); -void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag); -void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag); -void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag); - -float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity); // returns the number of traces done, for benchmarking - -void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity); - -/* -================== -findbetterlocation - -Returns a point at least 12 units away from walls -(useful for explosion animations, although the blast is performed where it really happened) -Ripped from DPMod -================== -*/ -vector findbetterlocation (vector org, float mindist); - -/* -================== -Angc used for animations -================== -*/ - - -float angc (float a1, float a2); - -.string lodtarget1; -.string lodtarget2; -.string lodmodel1; -.string lodmodel2; -.float lodmodelindex0; -.float lodmodelindex1; -.float lodmodelindex2; -.float loddistance1; -.float loddistance2; - -bool LOD_customize(entity this, entity client); - -void LOD_uncustomize(entity this); - -void LODmodel_attach(entity this); - -void ApplyMinMaxScaleAngles(entity e); - -void SetBrushEntityModel(entity this); - -void SetBrushEntityModelNoLOD(entity this); - -/* -================ -InitTrigger -================ -*/ - -void SetMovedir(entity this); - -void InitTrigger(entity this); - -void InitSolidBSPTrigger(entity this); - -bool InitMovingBrushTrigger(entity this); diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc index a613edc14..842f5b53c 100644 --- a/qcsrc/server/g_world.qc +++ b/qcsrc/server/g_world.qc @@ -13,7 +13,7 @@ #include "g_hook.qh" #include "ipban.qh" #include "mapvoting.qh" -#include "mutators/_mod.qh" +#include #include "race.qh" #include "scores.qh" #include "teamplay.qh" @@ -21,6 +21,7 @@ #include "../common/constants.qh" #include #include "../common/deathtypes/all.qh" +#include "../common/gamemodes/sv_rules.qh" #include "../common/mapinfo.qh" #include "../common/monsters/_mod.qh" #include "../common/monsters/sv_monsters.qh" @@ -30,8 +31,8 @@ #include "../common/playerstats.qh" #include "../common/stats.qh" #include "../common/teams.qh" -#include "../common/triggers/trigger/secret.qh" -#include "../common/triggers/target/music.qh" +#include "../common/mapobjects/trigger/secret.qh" +#include "../common/mapobjects/target/music.qh" #include "../common/util.qh" #include "../common/items/_mod.qh" #include @@ -91,7 +92,6 @@ const float SPAWNFLAG_NO_WAYPOINTS_FOR_ITEMS = 1; string redirection_target; float world_initialized; -string GetGametype(); void ShuffleMaplist(); void SetDefaultAlpha() @@ -505,38 +505,6 @@ void cvar_changes_init() cvar_purechanges = strzone(cvar_purechanges); } -void detect_maptype() -{ -#if 0 - vector o, v; - float i; - - for (;;) - { - o = world.mins; - o.x += random() * (world.maxs.x - world.mins.x); - o.y += random() * (world.maxs.y - world.mins.y); - o.z += random() * (world.maxs.z - world.mins.z); - - tracebox(o, STAT(PL_MIN), STAT(PL_MAX), o - '0 0 32768', MOVE_WORLDONLY, NULL); - if(trace_fraction == 1) - continue; - - v = trace_endpos; - - for(i = 0; i < 64; i += 4) - { - tracebox(o, '-1 -1 -1' * i, '1 1 1' * i, o - '0 0 32768', MOVE_WORLDONLY, NULL); - if(trace_fraction == 1) - continue; - LOG_INFO(ftos(i), " -> ", vtos(trace_endpos)); - } - - break; - } -#endif -} - entity randomseed; bool RandomSeed_Send(entity this, entity to, int sf) { @@ -869,8 +837,6 @@ spawnfunc(worldspawn) next_pingtime = time + 5; - detect_maptype(); - // set up information replies for clients and server to use maplist_reply = strzone(getmaplist()); lsmaps_reply = strzone(getlsmaps()); diff --git a/qcsrc/server/g_world.qh b/qcsrc/server/g_world.qh index 034407bc1..35ea5fe7d 100644 --- a/qcsrc/server/g_world.qh +++ b/qcsrc/server/g_world.qh @@ -16,6 +16,8 @@ void IntermissionThink(entity this); void GotoNextMap(float reinit); void ReadyRestart(); +string GetGametype(); + void DumpStats(float final); float Map_IsRecent(string m); string GetNextMap(); diff --git a/qcsrc/server/item_key.qc b/qcsrc/server/item_key.qc index c645c7fac..e939a5faa 100644 --- a/qcsrc/server/item_key.qc +++ b/qcsrc/server/item_key.qc @@ -1,6 +1,7 @@ #include "item_key.qh" -#include "../common/triggers/subs.qh" +#include "../common/mapobjects/subs.qh" +#include #include "../common/monsters/_mod.qh" #include "../common/notifications/all.qh" #include "../common/util.qh" diff --git a/qcsrc/server/items.qc b/qcsrc/server/items.qc index 04b0ba41d..b21df78e3 100644 --- a/qcsrc/server/items.qc +++ b/qcsrc/server/items.qc @@ -5,9 +5,9 @@ /// game items. /// \copyright GNU GPLv2 or any later version. -#include "g_subs.qh" -#include "mutators/events.qh" +#include #include +#include .bool m_isloot; ///< Holds whether item is loot. /// \brief Holds whether strength, shield or superweapon timers expire while diff --git a/qcsrc/server/miscfunctions.qc b/qcsrc/server/miscfunctions.qc index bbbefd5a8..1a885f78f 100644 --- a/qcsrc/server/miscfunctions.qc +++ b/qcsrc/server/miscfunctions.qc @@ -5,10 +5,11 @@ #include "constants.qh" #include "g_hook.qh" #include "ipban.qh" -#include "mutators/_mod.qh" +#include #include "../common/t_items.qh" #include "resources.qh" #include "items.qh" +#include "player.qh" #include "weapons/accuracy.qh" #include "weapons/csqcprojectile.qh" #include "weapons/selection.qh" @@ -20,7 +21,7 @@ #include "../common/notifications/all.qh" #include "../common/playerstats.qh" #include "../common/teams.qh" -#include "../common/triggers/subs.qh" +#include "../common/mapobjects/subs.qh" #include "../common/util.qh" #include "../common/turrets/sv_turrets.qh" #include @@ -198,10 +199,10 @@ string NearestLocation(vector p) return ret; } -string AmmoNameFromWeaponentity(entity wpn) +string AmmoNameFromWeaponentity(Weapon wep) { string ammoitems = "batteries"; - switch ((wpn.m_weapon).ammo_type) + switch (wep.ammo_type) { case RESOURCE_SHELLS: ammoitems = ITEM_Shells.m_name; break; case RESOURCE_BULLETS: ammoitems = ITEM_Bullets.m_name; break; @@ -266,7 +267,7 @@ string formatmessage(entity this, string msg) case "y": replacement = NearestLocation(cursor); break; case "d": replacement = NearestLocation(this.death_origin); break; case "w": replacement = ((this.(weaponentity).m_weapon == WEP_Null) ? ((this.(weaponentity).m_switchweapon == WEP_Null) ? Weapons_from(this.(weaponentity).cnt) : this.(weaponentity).m_switchweapon) : this.(weaponentity).m_weapon).m_name; break; - case "W": replacement = AmmoNameFromWeaponentity(this.(weaponentity)); break; + case "W": replacement = AmmoNameFromWeaponentity(this.(weaponentity).m_weapon); break; case "x": replacement = ((cursor_ent.netname == "" || !cursor_ent) ? "nothing" : cursor_ent.netname); break; case "s": replacement = ftos(vlen(this.velocity - this.velocity_z * '0 0 1')); break; case "S": replacement = ftos(vlen(this.velocity)); break; @@ -592,12 +593,6 @@ void readplayerstartcvars() g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3)); } - if(g_weaponarena) - g_weaponarena_random = cvar("g_weaponarena_random"); - else - g_weaponarena_random = 0; - g_weaponarena_random_with_blaster = cvar("g_weaponarena_random_with_blaster"); - if (g_weaponarena) { g_weapon_stay = 0; // incompatible diff --git a/qcsrc/server/miscfunctions.qh b/qcsrc/server/miscfunctions.qh index 41e725946..92707bf39 100644 --- a/qcsrc/server/miscfunctions.qh +++ b/qcsrc/server/miscfunctions.qh @@ -1,10 +1,11 @@ #pragma once #include +#include #include -#include "mutators/events.qh" +#include #include #include @@ -168,8 +169,7 @@ float g_pickup_fuel_max; float g_pickup_weapons_anyway; float g_weaponarena; WepSet g_weaponarena_weapons; -float g_weaponarena_random; -float g_weaponarena_random_with_blaster; +float g_weaponarena_random; // TODO string g_weaponarena_list; float g_weaponspeedfactor; float g_weaponratefactor; @@ -216,7 +216,6 @@ void readplayerstartcvars(); float sv_autotaunt; float sv_taunt; -string GetGametype(); // g_world.qc void readlevelcvars() { if(cvar("sv_allow_fullbright")) diff --git a/qcsrc/server/mutators/_mod.inc b/qcsrc/server/mutators/_mod.inc index f0108dec3..7b7cdf33d 100644 --- a/qcsrc/server/mutators/_mod.inc +++ b/qcsrc/server/mutators/_mod.inc @@ -1,4 +1,3 @@ // generated file; do not modify +#include #include - -#include diff --git a/qcsrc/server/mutators/_mod.qh b/qcsrc/server/mutators/_mod.qh index 9888c9466..6adf8e0db 100644 --- a/qcsrc/server/mutators/_mod.qh +++ b/qcsrc/server/mutators/_mod.qh @@ -1,4 +1,3 @@ // generated file; do not modify +#include #include - -#include diff --git a/qcsrc/server/mutators/events.qc b/qcsrc/server/mutators/events.qc new file mode 100644 index 000000000..c2dbb7021 --- /dev/null +++ b/qcsrc/server/mutators/events.qc @@ -0,0 +1 @@ +#include "events.qh" diff --git a/qcsrc/server/mutators/events.qh b/qcsrc/server/mutators/events.qh index 3f35fe9ea..cf39f337b 100644 --- a/qcsrc/server/mutators/events.qh +++ b/qcsrc/server/mutators/events.qh @@ -123,6 +123,8 @@ MUTATOR_HOOKABLE(ItemSound, EV_ItemSound); /** target */ i(entity, MUTATOR_ARGV_1_entity) \ /** frag score */ i(float, MUTATOR_ARGV_2_float) \ /** */ o(float, MUTATOR_ARGV_2_float) \ + /** deathtype */ i(float, MUTATOR_ARGV_3_float) \ + /** wep entity */ i(entity, MUTATOR_ARGV_4_entity) \ /**/ MUTATOR_HOOKABLE(GiveFragsForKill, EV_GiveFragsForKill); diff --git a/qcsrc/server/mutators/gamemode.qh b/qcsrc/server/mutators/gamemode.qh deleted file mode 100644 index b0f42f59e..000000000 --- a/qcsrc/server/mutators/gamemode.qh +++ /dev/null @@ -1,108 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include "mutator.qh" - -// TODO: trim - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include - -#include - -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -.float lastground; -float total_players; -float redalive, bluealive, yellowalive, pinkalive; diff --git a/qcsrc/server/mutators/mutator.qh b/qcsrc/server/mutators/mutator.qh deleted file mode 100644 index e051cd697..000000000 --- a/qcsrc/server/mutators/mutator.qh +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include diff --git a/qcsrc/server/mutators/mutator/_mod.inc b/qcsrc/server/mutators/mutator/_mod.inc deleted file mode 100644 index 6835f5d56..000000000 --- a/qcsrc/server/mutators/mutator/_mod.inc +++ /dev/null @@ -1,14 +0,0 @@ -// generated file; do not modify -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include diff --git a/qcsrc/server/mutators/mutator/_mod.qh b/qcsrc/server/mutators/mutator/_mod.qh deleted file mode 100644 index aef0b332a..000000000 --- a/qcsrc/server/mutators/mutator/_mod.qh +++ /dev/null @@ -1,14 +0,0 @@ -// generated file; do not modify -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include diff --git a/qcsrc/server/mutators/mutator/gamemode_assault.qc b/qcsrc/server/mutators/mutator/gamemode_assault.qc deleted file mode 100644 index d43dc9999..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_assault.qc +++ /dev/null @@ -1,627 +0,0 @@ -#include "gamemode_assault.qh" - -#include - -.entity sprite; -#define AS_ROUND_DELAY 5 - -IntrusiveList g_assault_destructibles; -IntrusiveList g_assault_objectivedecreasers; -IntrusiveList g_assault_objectives; -STATIC_INIT(g_assault) -{ - g_assault_destructibles = IL_NEW(); - g_assault_objectivedecreasers = IL_NEW(); - g_assault_objectives = IL_NEW(); -} - -// random functions -void assault_objective_use(entity this, entity actor, entity trigger) -{ - // activate objective - this.health = 100; - //print("^2Activated objective ", this.targetname, "=", etos(this), "\n"); - //print("Activator is ", actor.classname, "\n"); - - IL_EACH(g_assault_objectivedecreasers, it.target == this.targetname, - { - target_objective_decrease_activate(it); - }); -} - -vector target_objective_spawn_evalfunc(entity this, entity player, entity spot, vector current) -{ - if(this.health < 0 || this.health >= ASSAULT_VALUE_INACTIVE) - return '-1 0 0'; - return current; -} - -// reset this objective. Used when spawning an objective -// and when a new round starts -void assault_objective_reset(entity this) -{ - this.health = ASSAULT_VALUE_INACTIVE; -} - -// decrease the health of targeted objectives -void assault_objective_decrease_use(entity this, entity actor, entity trigger) -{ - if(actor.team != assault_attacker_team) - { - // wrong team triggered decrease - return; - } - - if(trigger.assault_sprite) - { - WaypointSprite_Disown(trigger.assault_sprite, waypointsprite_deadlifetime); - if(trigger.classname == "func_assault_destructible") - trigger.sprite = NULL; // TODO: just unsetting it?! - } - else - return; // already activated! cannot activate again! - - if(this.enemy.health < ASSAULT_VALUE_INACTIVE) - { - if(this.enemy.health - this.dmg > 0.5) - { - GameRules_scoring_add_team(actor, SCORE, this.dmg); - this.enemy.health = this.enemy.health - this.dmg; - } - else - { - GameRules_scoring_add_team(actor, SCORE, this.enemy.health); - GameRules_scoring_add_team(actor, ASSAULT_OBJECTIVES, 1); - this.enemy.health = -1; - - if(this.enemy.message) - FOREACH_CLIENT(IS_PLAYER(it), { centerprint(it, this.enemy.message); }); - - SUB_UseTargets(this.enemy, this, trigger); - } - } -} - -void assault_setenemytoobjective(entity this) -{ - IL_EACH(g_assault_objectives, it.targetname == this.target, - { - if(this.enemy == NULL) - this.enemy = it; - else - objerror(this, "more than one objective as target - fix the map!"); - break; - }); - - if(this.enemy == NULL) - objerror(this, "no objective as target - fix the map!"); -} - -bool assault_decreaser_sprite_visible(entity this, entity player, entity view) -{ - if(this.assault_decreaser.enemy.health >= ASSAULT_VALUE_INACTIVE) - return false; - - return true; -} - -void target_objective_decrease_activate(entity this) -{ - entity spr; - this.owner = NULL; - FOREACH_ENTITY_STRING(target, this.targetname, - { - if(it.assault_sprite != NULL) - { - WaypointSprite_Disown(it.assault_sprite, waypointsprite_deadlifetime); - if(it.classname == "func_assault_destructible") - it.sprite = NULL; // TODO: just unsetting it?! - } - - spr = WaypointSprite_SpawnFixed(WP_AssaultDefend, 0.5 * (it.absmin + it.absmax), it, assault_sprite, RADARICON_OBJECTIVE); - spr.assault_decreaser = this; - spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible; - spr.classname = "sprite_waypoint"; - WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY); - if(it.classname == "func_assault_destructible") - { - WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy); - WaypointSprite_UpdateMaxHealth(spr, it.max_health); - WaypointSprite_UpdateHealth(spr, it.health); - it.sprite = spr; - } - else - WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultPush, WP_AssaultPush); - }); -} - -void target_objective_decrease_findtarget(entity this) -{ - assault_setenemytoobjective(this); -} - -void target_assault_roundend_reset(entity this) -{ - //print("round end reset\n"); - ++this.cnt; // up round counter - this.winning = false; // up round -} - -void target_assault_roundend_use(entity this, entity actor, entity trigger) -{ - this.winning = 1; // round has been won by attackers -} - -void assault_roundstart_use(entity this, entity actor, entity trigger) -{ - SUB_UseTargets(this, this, trigger); - - //(Re)spawn all turrets - IL_EACH(g_turrets, true, - { - // Swap turret teams - if(it.team == NUM_TEAM_1) - it.team = NUM_TEAM_2; - else - it.team = NUM_TEAM_1; - - // Doubles as teamchange - turret_respawn(it); - }); -} -void assault_roundstart_use_this(entity this) -{ - assault_roundstart_use(this, NULL, NULL); -} - -void assault_wall_think(entity this) -{ - if(this.enemy.health < 0) - { - this.model = ""; - this.solid = SOLID_NOT; - } - else - { - this.model = this.mdl; - this.solid = SOLID_BSP; - } - - this.nextthink = time + 0.2; -} - -// trigger new round -// reset objectives, toggle spawnpoints, reset triggers, ... -void assault_new_round(entity this) -{ - //bprint("ASSAULT: new round\n"); - - // up round counter - this.winning = this.winning + 1; - - // swap attacker/defender roles - if(assault_attacker_team == NUM_TEAM_1) - assault_attacker_team = NUM_TEAM_2; - else - assault_attacker_team = NUM_TEAM_1; - - IL_EACH(g_saved_team, !IS_CLIENT(it), - { - if(it.team_saved == NUM_TEAM_1) - it.team_saved = NUM_TEAM_2; - else if(it.team_saved == NUM_TEAM_2) - it.team_saved = NUM_TEAM_1; - }); - - // reset the level with a countdown - cvar_set("timelimit", ftos(ceil(time - AS_ROUND_DELAY - game_starttime) / 60)); - ReadyRestart_force(); // sets game_starttime -} - -entity as_round; -.entity ent_winning; -void as_round_think() -{ - game_stopped = false; - assault_new_round(as_round.ent_winning); - delete(as_round); - as_round = NULL; -} - -// Assault winning condition: If the attackers triggered a round end (by fulfilling all objectives) -// they win. Otherwise the defending team wins once the timelimit passes. -int WinningCondition_Assault() -{ - if(as_round) - return WINNING_NO; - - WinningConditionHelper(NULL); // set worldstatus - - int status = WINNING_NO; - // as the timelimit has not yet passed just assume the defending team will win - if(assault_attacker_team == NUM_TEAM_1) - { - SetWinners(team, NUM_TEAM_2); - } - else - { - SetWinners(team, NUM_TEAM_1); - } - - entity ent; - ent = find(NULL, classname, "target_assault_roundend"); - if(ent) - { - if(ent.winning) // round end has been triggered by attacking team - { - bprint("Assault: round completed.\n"); - SetWinners(team, assault_attacker_team); - - TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 666 - TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 0)); - - if(ent.cnt == 1 || autocvar_g_campaign) // this was the second round - { - status = WINNING_YES; - } - else - { - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ASSAULT_OBJ_DESTROYED, ceil(time - game_starttime)); - as_round = new(as_round); - as_round.think = as_round_think; - as_round.ent_winning = ent; - as_round.nextthink = time + AS_ROUND_DELAY; - game_stopped = true; - - // make sure timelimit isn't hit while the game is blocked - if(autocvar_timelimit > 0) - if(time + AS_ROUND_DELAY >= game_starttime + autocvar_timelimit * 60) - cvar_set("timelimit", ftos(autocvar_timelimit + AS_ROUND_DELAY / 60)); - } - } - } - - return status; -} - -// spawnfuncs -spawnfunc(info_player_attacker) -{ - if (!g_assault) { delete(this); return; } - - this.team = NUM_TEAM_1; // red, gets swapped every round - spawnfunc_info_player_deathmatch(this); -} - -spawnfunc(info_player_defender) -{ - if (!g_assault) { delete(this); return; } - - this.team = NUM_TEAM_2; // blue, gets swapped every round - spawnfunc_info_player_deathmatch(this); -} - -spawnfunc(target_objective) -{ - if (!g_assault) { delete(this); return; } - - this.classname = "target_objective"; - IL_PUSH(g_assault_objectives, this); - this.use = assault_objective_use; - this.reset = assault_objective_reset; - this.reset(this); - this.spawn_evalfunc = target_objective_spawn_evalfunc; -} - -spawnfunc(target_objective_decrease) -{ - if (!g_assault) { delete(this); return; } - - this.classname = "target_objective_decrease"; - IL_PUSH(g_assault_objectivedecreasers, this); - - if(!this.dmg) - this.dmg = 101; - - this.use = assault_objective_decrease_use; - this.health = ASSAULT_VALUE_INACTIVE; - this.max_health = ASSAULT_VALUE_INACTIVE; - this.enemy = NULL; - - InitializeEntity(this, target_objective_decrease_findtarget, INITPRIO_FINDTARGET); -} - -// destructible walls that can be used to trigger target_objective_decrease -spawnfunc(func_breakable); -spawnfunc(func_assault_destructible) -{ - if (!g_assault) { delete(this); return; } - - this.spawnflags = 3; - this.classname = "func_assault_destructible"; - IL_PUSH(g_assault_destructibles, this); - - if(assault_attacker_team == NUM_TEAM_1) - this.team = NUM_TEAM_2; - else - this.team = NUM_TEAM_1; - - spawnfunc_func_breakable(this); -} - -spawnfunc(func_assault_wall) -{ - if (!g_assault) { delete(this); return; } - - this.classname = "func_assault_wall"; - this.mdl = this.model; - _setmodel(this, this.mdl); - this.solid = SOLID_BSP; - setthink(this, assault_wall_think); - this.nextthink = time; - InitializeEntity(this, assault_setenemytoobjective, INITPRIO_FINDTARGET); -} - -spawnfunc(target_assault_roundend) -{ - if (!g_assault) { delete(this); return; } - - this.winning = 0; // round not yet won by attackers - this.classname = "target_assault_roundend"; - this.use = target_assault_roundend_use; - this.cnt = 0; // first round - this.reset = target_assault_roundend_reset; -} - -spawnfunc(target_assault_roundstart) -{ - if (!g_assault) { delete(this); return; } - - assault_attacker_team = NUM_TEAM_1; - this.classname = "target_assault_roundstart"; - this.use = assault_roundstart_use; - this.reset2 = assault_roundstart_use_this; - InitializeEntity(this, assault_roundstart_use_this, INITPRIO_FINDTARGET); -} - -// legacy bot code -void havocbot_goalrating_ast_targets(entity this, float ratingscale) -{ - IL_EACH(g_assault_destructibles, it.bot_attack, - { - if (it.target == "") - continue; - - bool found = false; - entity destr = it; - IL_EACH(g_assault_objectivedecreasers, it.targetname == destr.target, - { - if(it.enemy.health > 0 && it.enemy.health < ASSAULT_VALUE_INACTIVE) - { - found = true; - break; - } - }); - - if(!found) - continue; - - vector p = 0.5 * (it.absmin + it.absmax); - - // Find and rate waypoints around it - found = false; - entity best = NULL; - float bestvalue = 99999999999; - entity des = it; - for(float radius = 0; radius < 1500 && !found; radius += 500) - { - FOREACH_ENTITY_RADIUS(p, radius, it.classname == "waypoint" && !(it.wpflags & WAYPOINTFLAG_GENERATED), - { - if(checkpvs(it.origin, des)) - { - found = true; - if(it.cnt < bestvalue) - { - best = it; - bestvalue = it.cnt; - } - } - }); - } - - if(best) - { - /// dprint("waypoints around target were found\n"); - // te_lightning2(NULL, '0 0 0', best.origin); - // te_knightspike(best.origin); - - navigation_routerating(this, best, ratingscale, 4000); - best.cnt += 1; - - this.havocbot_attack_time = 0; - - if(checkpvs(this.origin + this.view_ofs, it)) - if(checkpvs(this.origin + this.view_ofs, best)) - { - // dprint("increasing attack time for this target\n"); - this.havocbot_attack_time = time + 2; - } - } - }); -} - -void havocbot_role_ast_offense(entity this) -{ - if(IS_DEAD(this)) - { - this.havocbot_attack_time = 0; - havocbot_ast_reset_role(this); - return; - } - - // Set the role timeout if necessary - if (!this.havocbot_role_timeout) - this.havocbot_role_timeout = time + 120; - - if (time > this.havocbot_role_timeout) - { - havocbot_ast_reset_role(this); - return; - } - - if(this.havocbot_attack_time>time) - return; - - if (navigation_goalrating_timeout(this)) - { - navigation_goalrating_start(this); - havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650); - havocbot_goalrating_ast_targets(this, 20000); - havocbot_goalrating_items(this, 15000, this.origin, 10000); - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - } -} - -void havocbot_role_ast_defense(entity this) -{ - if(IS_DEAD(this)) - { - this.havocbot_attack_time = 0; - havocbot_ast_reset_role(this); - return; - } - - // Set the role timeout if necessary - if (!this.havocbot_role_timeout) - this.havocbot_role_timeout = time + 120; - - if (time > this.havocbot_role_timeout) - { - havocbot_ast_reset_role(this); - return; - } - - if(this.havocbot_attack_time>time) - return; - - if (navigation_goalrating_timeout(this)) - { - navigation_goalrating_start(this); - havocbot_goalrating_enemyplayers(this, 20000, this.origin, 3000); - havocbot_goalrating_ast_targets(this, 20000); - havocbot_goalrating_items(this, 15000, this.origin, 10000); - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - } -} - -void havocbot_role_ast_setrole(entity this, float role) -{ - switch(role) - { - case HAVOCBOT_AST_ROLE_DEFENSE: - this.havocbot_role = havocbot_role_ast_defense; - this.havocbot_role_flags = HAVOCBOT_AST_ROLE_DEFENSE; - this.havocbot_role_timeout = 0; - break; - case HAVOCBOT_AST_ROLE_OFFENSE: - this.havocbot_role = havocbot_role_ast_offense; - this.havocbot_role_flags = HAVOCBOT_AST_ROLE_OFFENSE; - this.havocbot_role_timeout = 0; - break; - } -} - -void havocbot_ast_reset_role(entity this) -{ - if(IS_DEAD(this)) - return; - - if(this.team == assault_attacker_team) - havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_OFFENSE); - else - havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_DEFENSE); -} - -// mutator hooks -MUTATOR_HOOKFUNCTION(as, PlayerSpawn) -{ - entity player = M_ARGV(0, entity); - - if(player.team == assault_attacker_team) - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_ATTACKING); - else - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_DEFENDING); -} - -MUTATOR_HOOKFUNCTION(as, TurretSpawn) -{ - entity turret = M_ARGV(0, entity); - - if(!turret.team || turret.team == FLOAT_MAX) - turret.team = 5; // this gets reversed when match starts? -} - -MUTATOR_HOOKFUNCTION(as, VehicleInit) -{ - entity veh = M_ARGV(0, entity); - - veh.nextthink = time + 0.5; -} - -MUTATOR_HOOKFUNCTION(as, HavocBot_ChooseRole) -{ - entity bot = M_ARGV(0, entity); - - havocbot_ast_reset_role(bot); - return true; -} - -MUTATOR_HOOKFUNCTION(as, PlayHitsound) -{ - entity frag_victim = M_ARGV(0, entity); - - return (frag_victim.classname == "func_assault_destructible"); -} - -MUTATOR_HOOKFUNCTION(as, CheckAllowedTeams) -{ - // assault always has 2 teams - c1 = c2 = 0; - return true; -} - -MUTATOR_HOOKFUNCTION(as, CheckRules_World) -{ - M_ARGV(0, float) = WinningCondition_Assault(); - return true; -} - -MUTATOR_HOOKFUNCTION(as, ReadLevelCvars) -{ - // incompatible - warmup_stage = 0; - sv_ready_restart_after_countdown = 0; -} - -MUTATOR_HOOKFUNCTION(as, OnEntityPreSpawn) -{ - entity ent = M_ARGV(0, entity); - - switch(ent.classname) - { - case "info_player_team1": - case "info_player_team2": - case "info_player_team3": - case "info_player_team4": - return true; - } -} - -MUTATOR_HOOKFUNCTION(as, ReadyRestart_Deny) -{ - // readyrestart not supported (yet) - return true; -} diff --git a/qcsrc/server/mutators/mutator/gamemode_assault.qh b/qcsrc/server/mutators/mutator/gamemode_assault.qh deleted file mode 100644 index ea714e6a2..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_assault.qh +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include "../gamemode.qh" - -const int ST_ASSAULT_OBJECTIVES = 1; - -REGISTER_MUTATOR(as, false) -{ - MUTATOR_STATIC(); - MUTATOR_ONADD - { - GameRules_teams(true); - int teams = BITS(2); // always red vs blue - GameRules_scoring(teams, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, { - field_team(ST_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY); - field(SP_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY); - }); - } - return 0; -} - -// sprites -.entity assault_decreaser; -.entity assault_sprite; - -// legacy bot defs -const int HAVOCBOT_AST_ROLE_NONE = 0; -const int HAVOCBOT_AST_ROLE_DEFENSE = 2; -const int HAVOCBOT_AST_ROLE_OFFENSE = 4; - -.int havocbot_role_flags; -.float havocbot_attack_time; - -void(entity this) havocbot_role_ast_defense; -void(entity this) havocbot_role_ast_offense; - -void(entity bot) havocbot_ast_reset_role; - -void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrating_items; -void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers; - -// predefined spawnfuncs -void target_objective_decrease_activate(entity this); diff --git a/qcsrc/server/mutators/mutator/gamemode_ca.qc b/qcsrc/server/mutators/mutator/gamemode_ca.qc deleted file mode 100644 index 176661ac9..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_ca.qc +++ /dev/null @@ -1,488 +0,0 @@ -#include "gamemode_ca.qh" - -float autocvar_g_ca_damage2score_multiplier; -bool autocvar_g_ca_spectate_enemies; - -void CA_count_alive_players() -{ - total_players = redalive = bluealive = yellowalive = pinkalive = 0; - FOREACH_CLIENT(IS_PLAYER(it), { - switch(it.team) - { - case NUM_TEAM_1: ++total_players; if(!IS_DEAD(it)) ++redalive; break; - case NUM_TEAM_2: ++total_players; if(!IS_DEAD(it)) ++bluealive; break; - case NUM_TEAM_3: ++total_players; if(!IS_DEAD(it)) ++yellowalive; break; - case NUM_TEAM_4: ++total_players; if(!IS_DEAD(it)) ++pinkalive; break; - } - }); - FOREACH_CLIENT(IS_REAL_CLIENT(it), { - STAT(REDALIVE, it) = redalive; - STAT(BLUEALIVE, it) = bluealive; - STAT(YELLOWALIVE, it) = yellowalive; - STAT(PINKALIVE, it) = pinkalive; - }); -} - -float CA_GetWinnerTeam() -{ - float winner_team = 0; - if(redalive >= 1) - winner_team = NUM_TEAM_1; - if(bluealive >= 1) - { - if(winner_team) return 0; - winner_team = NUM_TEAM_2; - } - if(yellowalive >= 1) - { - if(winner_team) return 0; - winner_team = NUM_TEAM_3; - } - if(pinkalive >= 1) - { - if(winner_team) return 0; - winner_team = NUM_TEAM_4; - } - if(winner_team) - return winner_team; - return -1; // no player left -} - -void nades_Clear(entity player); - -#define CA_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0)) -#define CA_ALIVE_TEAMS_OK() (CA_ALIVE_TEAMS() == NumTeams(ca_teams)) -float CA_CheckWinner() -{ - if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) - { - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER); - FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); }); - - allowed_to_spawn = false; - game_stopped = true; - round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit); - return 1; - } - - CA_count_alive_players(); - if(CA_ALIVE_TEAMS() > 1) - return 0; - - int winner_team = CA_GetWinnerTeam(); - if(winner_team > 0) - { - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN)); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN)); - TeamScore_AddToTeam(winner_team, ST_CA_ROUNDS, +1); - } - else if(winner_team == -1) - { - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED); - } - - allowed_to_spawn = false; - game_stopped = true; - round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit); - - FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); }); - - return 1; -} - -void CA_RoundStart() -{ - allowed_to_spawn = boolean(warmup_stage); -} - -bool CA_CheckTeams() -{ - static int prev_missing_teams_mask; - allowed_to_spawn = true; - CA_count_alive_players(); - if(CA_ALIVE_TEAMS_OK()) - { - if(prev_missing_teams_mask > 0) - Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS); - prev_missing_teams_mask = -1; - return true; - } - if(total_players == 0) - { - if(prev_missing_teams_mask > 0) - Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS); - prev_missing_teams_mask = -1; - return false; - } - int missing_teams_mask = 0; - if(ca_teams & BIT(0)) - missing_teams_mask += (!redalive) * 1; - if(ca_teams & BIT(1)) - missing_teams_mask += (!bluealive) * 2; - if(ca_teams & BIT(2)) - missing_teams_mask += (!yellowalive) * 4; - if(ca_teams & BIT(3)) - missing_teams_mask += (!pinkalive) * 8; - if(prev_missing_teams_mask != missing_teams_mask) - { - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask); - prev_missing_teams_mask = missing_teams_mask; - } - return false; -} - -bool ca_isEliminated(entity e) -{ - if(e.caplayer == 1 && (IS_DEAD(e) || e.frags == FRAGS_LMS_LOSER)) - return true; - if(e.caplayer == 0.5) - return true; - return false; -} - -/** Returns next available player to spectate if g_ca_spectate_enemies == 0 */ -entity CA_SpectateNext(entity player, entity start) -{ - if (SAME_TEAM(start, player)) return start; - // continue from current player - for (entity e = start; (e = find(e, classname, STR_PLAYER)); ) - { - if (SAME_TEAM(player, e)) return e; - } - // restart from begining - for (entity e = NULL; (e = find(e, classname, STR_PLAYER)); ) - { - if (SAME_TEAM(player, e)) return e; - } - return start; -} - - -MUTATOR_HOOKFUNCTION(ca, PlayerSpawn) -{ - entity player = M_ARGV(0, entity); - - player.caplayer = 1; - if (!warmup_stage) - eliminatedPlayers.SendFlags |= 1; -} - -MUTATOR_HOOKFUNCTION(ca, ForbidSpawn) -{ - entity player = M_ARGV(0, entity); - - // spectators / observers that weren't playing can join; they are - // immediately forced to observe in the PutClientInServer hook - // this way they are put in a team and can play in the next round - if (!allowed_to_spawn && player.caplayer) - return true; - return false; -} - -MUTATOR_HOOKFUNCTION(ca, PutClientInServer) -{ - entity player = M_ARGV(0, entity); - - if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join - { - TRANSMUTE(Observer, player); - if (CS(player).jointime != time && !player.caplayer) // not when connecting - { - player.caplayer = 0.5; - Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE); - } - } -} - -MUTATOR_HOOKFUNCTION(ca, reset_map_players) -{ - FOREACH_CLIENT(true, { - CS(it).killcount = 0; - if (!it.caplayer && IS_BOT_CLIENT(it)) - { - it.team = -1; - it.caplayer = 1; - } - if (it.caplayer) - { - TRANSMUTE(Player, it); - it.caplayer = 1; - PutClientInServer(it); - } - }); - bot_relinkplayerlist(); - return true; -} - -MUTATOR_HOOKFUNCTION(ca, ClientConnect) -{ - entity player = M_ARGV(0, entity); - - TRANSMUTE(Observer, player); - return true; -} - -MUTATOR_HOOKFUNCTION(ca, reset_map_global) -{ - allowed_to_spawn = true; - return true; -} - -MUTATOR_HOOKFUNCTION(ca, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE) -{ - M_ARGV(0, float) = ca_teams; -} - -entity ca_LastPlayerForTeam(entity this) -{ - entity last_pl = NULL; - FOREACH_CLIENT(IS_PLAYER(it) && it != this, { - if (!IS_DEAD(it)) - if (SAME_TEAM(this, it)) - if (!last_pl) - last_pl = it; - else - return NULL; - }); - return last_pl; -} - -void ca_LastPlayerForTeam_Notify(entity this) -{ - if (round_handler_IsActive()) - if (round_handler_IsRoundStarted()) - { - entity pl = ca_LastPlayerForTeam(this); - if (pl) - Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE); - } -} - -MUTATOR_HOOKFUNCTION(ca, PlayerDies) -{ - entity frag_target = M_ARGV(2, entity); - - ca_LastPlayerForTeam_Notify(frag_target); - if (!allowed_to_spawn) - { - frag_target.respawn_flags = RESPAWN_SILENT; - // prevent unwanted sudden rejoin as spectator and movement of spectator camera - frag_target.respawn_time = time + 2; - } - frag_target.respawn_flags |= RESPAWN_FORCE; - if (!warmup_stage) - eliminatedPlayers.SendFlags |= 1; - if(IS_BOT_CLIENT(frag_target)) - bot_clear(frag_target); - return true; -} - -MUTATOR_HOOKFUNCTION(ca, ClientDisconnect) -{ - entity player = M_ARGV(0, entity); - - if (player.caplayer == 1) - ca_LastPlayerForTeam_Notify(player); - return true; -} - -MUTATOR_HOOKFUNCTION(ca, MakePlayerObserver) -{ - entity player = M_ARGV(0, entity); - - if (!IS_DEAD(player)) - ca_LastPlayerForTeam_Notify(player); - if (player.killindicator_teamchange == -2) // player wants to spectate - player.caplayer = 0; - if (player.caplayer) - player.frags = FRAGS_LMS_LOSER; - if (!warmup_stage) - eliminatedPlayers.SendFlags |= 1; - if (!player.caplayer) - return false; // allow team reset - return true; // prevent team reset -} - -MUTATOR_HOOKFUNCTION(ca, ForbidThrowCurrentWeapon) -{ - return true; -} - -MUTATOR_HOOKFUNCTION(ca, GiveFragsForKill, CBC_ORDER_FIRST) -{ - M_ARGV(2, float) = 0; // score will be given to the winner team when the round ends - return true; -} - -MUTATOR_HOOKFUNCTION(ca, SetStartItems) -{ - start_items &= ~IT_UNLIMITED_AMMO; - start_health = warmup_start_health = cvar("g_lms_start_health"); - start_armorvalue = warmup_start_armorvalue = cvar("g_lms_start_armor"); - start_ammo_shells = warmup_start_ammo_shells = cvar("g_lms_start_ammo_shells"); - start_ammo_nails = warmup_start_ammo_nails = cvar("g_lms_start_ammo_nails"); - start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets"); - start_ammo_cells = warmup_start_ammo_cells = cvar("g_lms_start_ammo_cells"); - start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_lms_start_ammo_plasma"); - start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_lms_start_ammo_fuel"); -} - -MUTATOR_HOOKFUNCTION(ca, Damage_Calculate) -{ - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - float frag_deathtype = M_ARGV(3, float); - float frag_damage = M_ARGV(4, float); - float frag_mirrordamage = M_ARGV(5, float); - - if (IS_PLAYER(frag_target)) - if (!IS_DEAD(frag_target)) - if (frag_target == frag_attacker || SAME_TEAM(frag_target, frag_attacker) || frag_deathtype == DEATH_FALL.m_id) - frag_damage = 0; - - frag_mirrordamage = 0; - - M_ARGV(4, float) = frag_damage; - M_ARGV(5, float) = frag_mirrordamage; -} - -MUTATOR_HOOKFUNCTION(ca, FilterItem) -{ - entity item = M_ARGV(0, entity); - - if (autocvar_g_powerups <= 0) - if (item.flags & FL_POWERUP) - return true; - - if (autocvar_g_pickup_items <= 0) - return true; -} - -MUTATOR_HOOKFUNCTION(ca, PlayerDamage_SplitHealthArmor) -{ - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - float frag_damage = M_ARGV(7, float); - float damage_take = M_ARGV(4, float); - float damage_save = M_ARGV(5, float); - - float excess = max(0, frag_damage - damage_take - damage_save); - - if (frag_target != frag_attacker && IS_PLAYER(frag_attacker)) - GameRules_scoring_add_team(frag_attacker, SCORE, (frag_damage - excess) * autocvar_g_ca_damage2score_multiplier); -} - -MUTATOR_HOOKFUNCTION(ca, CalculateRespawnTime) -{ - // no respawn calculations needed, player is forced to spectate anyway - return true; -} - -MUTATOR_HOOKFUNCTION(ca, PlayerRegen) -{ - // no regeneration in CA - return true; -} - -MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining) -{ - // announce remaining frags - return true; -} - -MUTATOR_HOOKFUNCTION(ca, SpectateSet) -{ - entity client = M_ARGV(0, entity); - entity targ = M_ARGV(1, entity); - - if (!autocvar_g_ca_spectate_enemies && client.caplayer) - if (DIFF_TEAM(targ, client)) - return true; -} - -MUTATOR_HOOKFUNCTION(ca, SpectateNext) -{ - entity client = M_ARGV(0, entity); - - if (!autocvar_g_ca_spectate_enemies && client.caplayer) - { - entity targ = M_ARGV(1, entity); - M_ARGV(1, entity) = CA_SpectateNext(client, targ); - return true; - } -} - -MUTATOR_HOOKFUNCTION(ca, SpectatePrev) -{ - entity client = M_ARGV(0, entity); - entity targ = M_ARGV(1, entity); - entity first = M_ARGV(2, entity); - - if (!autocvar_g_ca_spectate_enemies && client.caplayer) - { - do { targ = targ.chain; } - while(targ && DIFF_TEAM(targ, client)); - - if (!targ) - { - for (targ = first; targ && DIFF_TEAM(targ, client); targ = targ.chain); - - if (targ == client.enemy) - return MUT_SPECPREV_RETURN; - } - } - - M_ARGV(1, entity) = targ; - - return MUT_SPECPREV_FOUND; -} - -MUTATOR_HOOKFUNCTION(ca, Bot_FixCount, CBC_ORDER_EXCLUSIVE) -{ - FOREACH_CLIENT(IS_REAL_CLIENT(it), { - if (IS_PLAYER(it) || it.caplayer == 1) - ++M_ARGV(0, int); - ++M_ARGV(1, int); - }); - return true; -} - -MUTATOR_HOOKFUNCTION(ca, ClientCommand_Spectate) -{ - entity player = M_ARGV(0, entity); - - if (player.caplayer) - { - // they're going to spec, we can do other checks - if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player))) - Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE); - return MUT_SPECCMD_FORCE; - } - - return MUT_SPECCMD_CONTINUE; -} - -MUTATOR_HOOKFUNCTION(ca, WantWeapon) -{ - M_ARGV(2, bool) = true; // all weapons -} - -MUTATOR_HOOKFUNCTION(ca, HideTeamNagger) -{ - return true; // doesn't work well with the whole spectator as player thing -} - -MUTATOR_HOOKFUNCTION(ca, GetPlayerStatus) -{ - entity player = M_ARGV(0, entity); - - return player.caplayer == 1; -} - -MUTATOR_HOOKFUNCTION(ca, SetWeaponArena) -{ - // most weapons arena - if (M_ARGV(0, string) == "0" || M_ARGV(0, string) == "") M_ARGV(0, string) = "most"; -} diff --git a/qcsrc/server/mutators/mutator/gamemode_ca.qh b/qcsrc/server/mutators/mutator/gamemode_ca.qh deleted file mode 100644 index 0982fcca8..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_ca.qh +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include "../gamemode.qh" - -int autocvar_g_ca_point_limit; -int autocvar_g_ca_point_leadlimit; -float autocvar_g_ca_round_timelimit; -bool autocvar_g_ca_team_spawns; -//int autocvar_g_ca_teams; -int autocvar_g_ca_teams_override; -float autocvar_g_ca_warmup; - - -int ca_teams; -bool allowed_to_spawn; - -const int ST_CA_ROUNDS = 1; - -bool CA_CheckTeams(); -bool CA_CheckWinner(); -void CA_RoundStart(); -bool ca_isEliminated(entity e); - -REGISTER_MUTATOR(ca, false) -{ - MUTATOR_STATIC(); - MUTATOR_ONADD - { - GameRules_teams(true); - GameRules_spawning_teams(autocvar_g_ca_team_spawns); - GameRules_limit_score(autocvar_g_ca_point_limit); - GameRules_limit_lead(autocvar_g_ca_point_leadlimit); - - ca_teams = autocvar_g_ca_teams_override; - if (ca_teams < 2) - ca_teams = cvar("g_ca_teams"); // read the cvar directly as it gets written earlier in the same frame - - ca_teams = BITS(bound(2, ca_teams, 4)); - GameRules_scoring(ca_teams, SFL_SORT_PRIO_PRIMARY, 0, { - field_team(ST_CA_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY); - }); - - allowed_to_spawn = true; - round_handler_Spawn(CA_CheckTeams, CA_CheckWinner, CA_RoundStart); - round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit); - EliminatedPlayers_Init(ca_isEliminated); - } - return 0; -} - -// should be removed in the future, as other code should not have to care -.float caplayer; // 0.5 if scheduled to join the next round diff --git a/qcsrc/server/mutators/mutator/gamemode_ctf.qc b/qcsrc/server/mutators/mutator/gamemode_ctf.qc deleted file mode 100644 index e0d25e9e8..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_ctf.qc +++ /dev/null @@ -1,2774 +0,0 @@ -#include "gamemode_ctf.qh" - -#include -#include -#include - -#include - -bool autocvar_g_ctf_allow_vehicle_carry; -bool autocvar_g_ctf_allow_vehicle_touch; -bool autocvar_g_ctf_allow_monster_touch; -bool autocvar_g_ctf_throw; -float autocvar_g_ctf_throw_angle_max; -float autocvar_g_ctf_throw_angle_min; -int autocvar_g_ctf_throw_punish_count; -float autocvar_g_ctf_throw_punish_delay; -float autocvar_g_ctf_throw_punish_time; -float autocvar_g_ctf_throw_strengthmultiplier; -float autocvar_g_ctf_throw_velocity_forward; -float autocvar_g_ctf_throw_velocity_up; -float autocvar_g_ctf_drop_velocity_up; -float autocvar_g_ctf_drop_velocity_side; -bool autocvar_g_ctf_oneflag_reverse; -bool autocvar_g_ctf_portalteleport; -bool autocvar_g_ctf_pass; -float autocvar_g_ctf_pass_arc; -float autocvar_g_ctf_pass_arc_max; -float autocvar_g_ctf_pass_directional_max; -float autocvar_g_ctf_pass_directional_min; -float autocvar_g_ctf_pass_radius; -float autocvar_g_ctf_pass_wait; -bool autocvar_g_ctf_pass_request; -float autocvar_g_ctf_pass_turnrate; -float autocvar_g_ctf_pass_timelimit; -float autocvar_g_ctf_pass_velocity; -bool autocvar_g_ctf_dynamiclights; -float autocvar_g_ctf_flag_collect_delay; -float autocvar_g_ctf_flag_damageforcescale; -bool autocvar_g_ctf_flag_dropped_waypoint; -bool autocvar_g_ctf_flag_dropped_floatinwater; -bool autocvar_g_ctf_flag_glowtrails; -int autocvar_g_ctf_flag_health; -bool autocvar_g_ctf_flag_return; -bool autocvar_g_ctf_flag_return_carrying; -float autocvar_g_ctf_flag_return_carried_radius; -float autocvar_g_ctf_flag_return_time; -bool autocvar_g_ctf_flag_return_when_unreachable; -float autocvar_g_ctf_flag_return_damage; -float autocvar_g_ctf_flag_return_damage_delay; -float autocvar_g_ctf_flag_return_dropped; -float autocvar_g_ctf_flagcarrier_auto_helpme_damage; -float autocvar_g_ctf_flagcarrier_auto_helpme_time; -float autocvar_g_ctf_flagcarrier_selfdamagefactor; -float autocvar_g_ctf_flagcarrier_selfforcefactor; -float autocvar_g_ctf_flagcarrier_damagefactor; -float autocvar_g_ctf_flagcarrier_forcefactor; -//float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting; -bool autocvar_g_ctf_fullbrightflags; -bool autocvar_g_ctf_ignore_frags; -bool autocvar_g_ctf_score_ignore_fields; -int autocvar_g_ctf_score_capture; -int autocvar_g_ctf_score_capture_assist; -int autocvar_g_ctf_score_kill; -int autocvar_g_ctf_score_penalty_drop; -int autocvar_g_ctf_score_penalty_returned; -int autocvar_g_ctf_score_pickup_base; -int autocvar_g_ctf_score_pickup_dropped_early; -int autocvar_g_ctf_score_pickup_dropped_late; -int autocvar_g_ctf_score_return; -float autocvar_g_ctf_shield_force; -float autocvar_g_ctf_shield_max_ratio; -int autocvar_g_ctf_shield_min_negscore; -bool autocvar_g_ctf_stalemate; -int autocvar_g_ctf_stalemate_endcondition; -float autocvar_g_ctf_stalemate_time; -bool autocvar_g_ctf_reverse; -float autocvar_g_ctf_dropped_capture_delay; -float autocvar_g_ctf_dropped_capture_radius; - -void ctf_FakeTimeLimit(entity e, float t) -{ - msg_entity = e; - WriteByte(MSG_ONE, 3); // svc_updatestat - WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT - if(t < 0) - WriteCoord(MSG_ONE, autocvar_timelimit); - else - WriteCoord(MSG_ONE, (t + 1) / 60); -} - -void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later -{ - if(autocvar_sv_eventlog) - GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != NULL) ? ftos(actor.playerid) : ""))); - //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : ""))); -} - -void ctf_CaptureRecord(entity flag, entity player) -{ - float cap_record = ctf_captimerecord; - float cap_time = (time - flag.ctf_pickuptime); - string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname")); - - // notify about shit - if(ctf_oneflag) - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); - else if(!ctf_captimerecord) - Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, TIME_ENCODE(cap_time)); - else if(cap_time < cap_record) - Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_BROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record)); - else - Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_UNBROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record)); - - // write that shit in the database - if(!ctf_oneflag) // but not in 1-flag mode - if((!ctf_captimerecord) || (cap_time < cap_record)) - { - ctf_captimerecord = cap_time; - db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time)); - db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname); - write_recordmarker(player, flag.ctf_pickuptime, cap_time); - } - - if(autocvar_g_ctf_leaderboard && !ctf_oneflag) - race_setTime(GetMapname(), TIME_ENCODE(cap_time), player.crypto_idfp, player.netname, player, false); -} - -bool ctf_Immediate_Return_Allowed(entity flag, entity toucher) -{ - int num_perteam = 0; - FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; }); - - // automatically return if there's only 1 player on the team - return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried)) - && flag.team); -} - -bool ctf_Return_Customize(entity this, entity client) -{ - // only to the carrier - return boolean(client == this.owner); -} - -void ctf_FlagcarrierWaypoints(entity player) -{ - WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG); - WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2); - WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id)); - WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team)); - - if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried)) - { - if(!player.wps_enemyflagcarrier) - { - entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG); - wp.colormod = WPCOLOR_ENEMYFC(player.team); - setcefc(wp, ctf_Stalemate_Customize); - - if(IS_REAL_CLIENT(player) && !ctf_stalemate) - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE); - } - - if(!player.wps_flagreturn) - { - entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG); - owp.colormod = '0 0.8 0.8'; - //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1')); - setcefc(owp, ctf_Return_Customize); - } - } -} - -void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate) -{ - float current_distance = vlen((('1 0 0' * to.x) + ('0 1 0' * to.y)) - (('1 0 0' * from.x) + ('0 1 0' * from.y))); // for the sake of this check, exclude Z axis - float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc))); - float current_height = (initial_height * min(1, (current_distance / flag.pass_distance))); - //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n"); - - vector targpos; - if(current_height) // make sure we can actually do this arcing path - { - targpos = (to + ('0 0 1' * current_height)); - WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag); - if(trace_fraction < 1) - { - //print("normal arc line failed, trying to find new pos..."); - WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag); - targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET); - WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag); - if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ } - /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */ - } - } - else { targpos = to; } - - //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y)); - - vector desired_direction = normalize(targpos - from); - if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); } - else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); } -} - -bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer) -{ - if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min) - { - // directional tracing only - float spreadlimit; - makevectors(passer_angle); - - // find the closest point on the enemy to the center of the attack - float h; // hypotenuse, which is the distance between attacker to head - float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin - - h = vlen(head_center - passer_center); - a = h * (normalize(head_center - passer_center) * v_forward); - - vector nearest_on_line = (passer_center + a * v_forward); - float distance_from_line = vlen(nearest_to_passer - nearest_on_line); - - spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1); - spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit); - - if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90)) - { return true; } - else - { return false; } - } - else { return true; } -} - - -// ======================= -// CaptureShield Functions -// ======================= - -bool ctf_CaptureShield_CheckStatus(entity p) -{ - int s, s2, s3, s4, se, se2, se3, se4, sr, ser; - int players_worseeq, players_total; - - if(ctf_captureshield_max_ratio <= 0) - return false; - - s = GameRules_scoring_add(p, CTF_CAPS, 0); - s2 = GameRules_scoring_add(p, CTF_PICKUPS, 0); - s3 = GameRules_scoring_add(p, CTF_RETURNS, 0); - s4 = GameRules_scoring_add(p, CTF_FCKILLS, 0); - - sr = ((s - s2) + (s3 + s4)); - - if(sr >= -ctf_captureshield_min_negscore) - return false; - - players_total = players_worseeq = 0; - FOREACH_CLIENT(IS_PLAYER(it), { - if(DIFF_TEAM(it, p)) - continue; - se = GameRules_scoring_add(it, CTF_CAPS, 0); - se2 = GameRules_scoring_add(it, CTF_PICKUPS, 0); - se3 = GameRules_scoring_add(it, CTF_RETURNS, 0); - se4 = GameRules_scoring_add(it, CTF_FCKILLS, 0); - - ser = ((se - se2) + (se3 + se4)); - - if(ser <= sr) - ++players_worseeq; - ++players_total; - }); - - // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse - // use this rule here - - if(players_worseeq >= players_total * ctf_captureshield_max_ratio) - return false; - - return true; -} - -void ctf_CaptureShield_Update(entity player, bool wanted_status) -{ - bool updated_status = ctf_CaptureShield_CheckStatus(player); - if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only - { - Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE)); - player.ctf_captureshielded = updated_status; - } -} - -bool ctf_CaptureShield_Customize(entity this, entity client) -{ - if(!client.ctf_captureshielded) { return false; } - if(CTF_SAMETEAM(this, client)) { return false; } - - return true; -} - -void ctf_CaptureShield_Touch(entity this, entity toucher) -{ - if(!toucher.ctf_captureshielded) { return; } - if(CTF_SAMETEAM(this, toucher)) { return; } - - vector mymid = (this.absmin + this.absmax) * 0.5; - vector theirmid = (toucher.absmin + toucher.absmax) * 0.5; - - Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, mymid, normalize(theirmid - mymid) * ctf_captureshield_force); - if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); } -} - -void ctf_CaptureShield_Spawn(entity flag) -{ - entity shield = new(ctf_captureshield); - - shield.enemy = flag; - shield.team = flag.team; - settouch(shield, ctf_CaptureShield_Touch); - setcefc(shield, ctf_CaptureShield_Customize); - shield.effects = EF_ADDITIVE; - set_movetype(shield, MOVETYPE_NOCLIP); - shield.solid = SOLID_TRIGGER; - shield.avelocity = '7 0 11'; - shield.scale = 0.5; - - setorigin(shield, flag.origin); - setmodel(shield, MDL_CTF_SHIELD); - setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs); -} - - -// ==================== -// Drop/Pass/Throw Code -// ==================== - -void ctf_Handle_Drop(entity flag, entity player, int droptype) -{ - // declarations - player = (player ? player : flag.pass_sender); - - // main - set_movetype(flag, MOVETYPE_TOSS); - flag.takedamage = DAMAGE_YES; - flag.angles = '0 0 0'; - flag.health = flag.max_flag_health; - flag.ctf_droptime = time; - flag.ctf_dropper = player; - flag.ctf_status = FLAG_DROPPED; - - // messages and sounds - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname); - _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE); - ctf_EventLog("dropped", player.team, player); - - // scoring - GameRules_scoring_add_team(player, SCORE, -((flag.score_drop) ? flag.score_drop : autocvar_g_ctf_score_penalty_drop)); - GameRules_scoring_add(player, CTF_DROPS, 1); - - // waypoints - if(autocvar_g_ctf_flag_dropped_waypoint) { - entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, NULL, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, true, RADARICON_FLAG); - wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team); - } - - if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health)) - { - WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health); - WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); - } - - player.throw_antispam = time + autocvar_g_ctf_pass_wait; - - if(droptype == DROP_PASS) - { - flag.pass_distance = 0; - flag.pass_sender = NULL; - flag.pass_target = NULL; - } -} - -void ctf_Handle_Retrieve(entity flag, entity player) -{ - entity sender = flag.pass_sender; - - // transfer flag to player - flag.owner = player; - flag.owner.flagcarried = flag; - GameRules_scoring_vip(player, true); - - // reset flag - if(player.vehicle) - { - setattachment(flag, player.vehicle, ""); - setorigin(flag, VEHICLE_FLAG_OFFSET); - flag.scale = VEHICLE_FLAG_SCALE; - } - else - { - setattachment(flag, player, ""); - setorigin(flag, FLAG_CARRY_OFFSET); - } - set_movetype(flag, MOVETYPE_NONE); - flag.takedamage = DAMAGE_NO; - flag.solid = SOLID_NOT; - flag.angles = '0 0 0'; - flag.ctf_status = FLAG_CARRY; - - // messages and sounds - _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM); - ctf_EventLog("receive", flag.team, player); - - FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { - if(it == sender) - Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname); - else if(it == player) - Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname); - else if(SAME_TEAM(it, sender)) - Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), sender.netname, player.netname); - }); - - // create new waypoint - ctf_FlagcarrierWaypoints(player); - - sender.throw_antispam = time + autocvar_g_ctf_pass_wait; - player.throw_antispam = sender.throw_antispam; - - flag.pass_distance = 0; - flag.pass_sender = NULL; - flag.pass_target = NULL; -} - -void ctf_Handle_Throw(entity player, entity receiver, int droptype) -{ - entity flag = player.flagcarried; - vector targ_origin, flag_velocity; - - if(!flag) { return; } - if((droptype == DROP_PASS) && !receiver) { return; } - - if(flag.speedrunning) { ctf_RespawnFlag(flag); return; } - - // reset the flag - setattachment(flag, NULL, ""); - setorigin(flag, player.origin + FLAG_DROP_OFFSET); - flag.owner.flagcarried = NULL; - GameRules_scoring_vip(flag.owner, false); - flag.owner = NULL; - flag.solid = SOLID_TRIGGER; - flag.ctf_dropper = player; - flag.ctf_droptime = time; - navigation_dynamicgoal_set(flag); - - flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS - - switch(droptype) - { - case DROP_PASS: - { - // warpzone support: - // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver - // findradius has already put wzn ... wz1 into receiver's warpzone parameters! - WarpZone_RefSys_Copy(flag, receiver); - WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver - targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag - - flag.pass_distance = vlen((('1 0 0' * targ_origin.x) + ('0 1 0' * targ_origin.y)) - (('1 0 0' * player.origin.x) + ('0 1 0' * player.origin.y))); // for the sake of this check, exclude Z axis - ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false); - - // main - set_movetype(flag, MOVETYPE_FLY); - flag.takedamage = DAMAGE_NO; - flag.pass_sender = player; - flag.pass_target = receiver; - flag.ctf_status = FLAG_PASSING; - - // other - _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM); - WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin); - ctf_EventLog("pass", flag.team, player); - break; - } - - case DROP_THROW: - { - makevectors((player.v_angle.y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle.x, autocvar_g_ctf_throw_angle_max) * '1 0 0')); - - flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & ITEM_Strength.m_itemid) ? autocvar_g_ctf_throw_strengthmultiplier : 1))); - flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false); - ctf_Handle_Drop(flag, player, droptype); - break; - } - - case DROP_RESET: - { - flag.velocity = '0 0 0'; // do nothing - break; - } - - default: - case DROP_NORMAL: - { - flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), false); - ctf_Handle_Drop(flag, player, droptype); - break; - } - } - - // kill old waypointsprite - WaypointSprite_Ping(player.wps_flagcarrier); - WaypointSprite_Kill(player.wps_flagcarrier); - - if(player.wps_enemyflagcarrier) - WaypointSprite_Kill(player.wps_enemyflagcarrier); - - if(player.wps_flagreturn) - WaypointSprite_Kill(player.wps_flagreturn); - - // captureshield - ctf_CaptureShield_Update(player, 0); // shield player from picking up flag -} - -void shockwave_spawn(string m, vector org, float sz, float t1, float t2) -{ - return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2); -} - -// ============== -// Event Handlers -// ============== - -void nades_GiveBonus(entity player, float score); - -void ctf_Handle_Capture(entity flag, entity toucher, int capturetype) -{ - entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher); - entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper); - entity player_team_flag = NULL, tmp_entity; - float old_time, new_time; - - if(!player) { return; } // without someone to give the reward to, we can't possibly cap - if(CTF_DIFFTEAM(player, flag)) { return; } - if((flag.cnt || enemy_flag.cnt) && flag.cnt != enemy_flag.cnt) { return; } // this should catch some edge cases (capturing grouped flag at ungrouped flag disallowed etc) - - if (toucher.goalentity == flag.bot_basewaypoint) - toucher.goalentity_lock_timeout = 0; - - if(ctf_oneflag) - for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) - if(SAME_TEAM(tmp_entity, player)) - { - player_team_flag = tmp_entity; - break; - } - - nades_GiveBonus(player, autocvar_g_nades_bonus_score_high ); - - player.throw_prevtime = time; - player.throw_count = 0; - - // messages and sounds - Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE)); - ctf_CaptureRecord(enemy_flag, player); - _sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE); - - switch(capturetype) - { - case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break; - case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break; - default: break; - } - - // scoring - float pscore = 0; - if(enemy_flag.score_capture || flag.score_capture) - pscore = floor((max(1, enemy_flag.score_capture) + max(1, flag.score_capture)) * 0.5); - GameRules_scoring_add_team(player, SCORE, ((pscore) ? pscore : autocvar_g_ctf_score_capture)); - float capscore = 0; - if(enemy_flag.score_team_capture || flag.score_team_capture) - capscore = floor((max(1, enemy_flag.score_team_capture) + max(1, flag.score_team_capture)) * 0.5); - GameRules_scoring_add_team(player, CTF_CAPS, ((capscore) ? capscore : 1)); - - old_time = GameRules_scoring_add(player, CTF_CAPTIME, 0); - new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime); - if(!old_time || new_time < old_time) - GameRules_scoring_add(player, CTF_CAPTIME, new_time - old_time); - - // effects - Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1); - //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1); - - // other - if(capturetype == CAPTURE_NORMAL) - { - WaypointSprite_Kill(player.wps_flagcarrier); - if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); } - - if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper)) - { GameRules_scoring_add_team(enemy_flag.ctf_dropper, SCORE, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); } - } - - flag.enemy = toucher; - - // reset the flag - player.next_take_time = time + autocvar_g_ctf_flag_collect_delay; - ctf_RespawnFlag(enemy_flag); -} - -void ctf_Handle_Return(entity flag, entity player) -{ - // messages and sounds - if(IS_MONSTER(player)) - { - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name); - } - else if(flag.team) - { - Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN)); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN), player.netname); - } - _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE); - ctf_EventLog("return", flag.team, player); - - // scoring - if(IS_PLAYER(player)) - { - GameRules_scoring_add_team(player, SCORE, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return - GameRules_scoring_add(player, CTF_RETURNS, 1); // add to count of returns - - nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium); - } - - TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it - - if(flag.ctf_dropper) - { - GameRules_scoring_add(flag.ctf_dropper, SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag - ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag - flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time - } - - // other - if(player.flagcarried == flag) - WaypointSprite_Kill(player.wps_flagcarrier); - - flag.enemy = player; - - // reset the flag - ctf_RespawnFlag(flag); -} - -void ctf_Handle_Pickup(entity flag, entity player, int pickuptype) -{ - // declarations - float pickup_dropped_score; // used to calculate dropped pickup score - - // attach the flag to the player - flag.owner = player; - player.flagcarried = flag; - GameRules_scoring_vip(player, true); - if(player.vehicle) - { - setattachment(flag, player.vehicle, ""); - setorigin(flag, VEHICLE_FLAG_OFFSET); - flag.scale = VEHICLE_FLAG_SCALE; - } - else - { - setattachment(flag, player, ""); - setorigin(flag, FLAG_CARRY_OFFSET); - } - - // flag setup - set_movetype(flag, MOVETYPE_NONE); - flag.takedamage = DAMAGE_NO; - flag.solid = SOLID_NOT; - flag.angles = '0 0 0'; - flag.ctf_status = FLAG_CARRY; - - switch(pickuptype) - { - case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs - case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit - default: break; - } - - // messages and sounds - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), player.netname); - if(ctf_stalemate) - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); - if(!flag.team) - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); - else if(CTF_DIFFTEAM(player, flag)) - Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_PICKUP)); - else - Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team)); - - Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname); - - if(!flag.team) - FOREACH_CLIENT(IS_PLAYER(it) && it != player && DIFF_TEAM(it, player), { Send_Notification(NOTIF_ONE, it, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname); }); - - if(flag.team) - FOREACH_CLIENT(IS_PLAYER(it) && it != player, { - if(CTF_SAMETEAM(flag, it)) - if(SAME_TEAM(player, it)) - Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname); - else - Send_Notification(NOTIF_ONE, it, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname); - }); - - _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE); - - // scoring - GameRules_scoring_add(player, CTF_PICKUPS, 1); - nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor); - switch(pickuptype) - { - case PICKUP_BASE: - { - GameRules_scoring_add_team(player, SCORE, ((flag.score_pickup) ? flag.score_pickup : autocvar_g_ctf_score_pickup_base)); - ctf_EventLog("steal", flag.team, player); - break; - } - - case PICKUP_DROPPED: - { - pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1); - pickup_dropped_score = floor((autocvar_g_ctf_score_pickup_dropped_late * (1 - pickup_dropped_score) + autocvar_g_ctf_score_pickup_dropped_early * pickup_dropped_score) + 0.5); - LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score)); - GameRules_scoring_add_team(player, SCORE, pickup_dropped_score); - ctf_EventLog("pickup", flag.team, player); - break; - } - - default: break; - } - - // speedrunning - if(pickuptype == PICKUP_BASE) - { - flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record - if((player.speedrunning) && (ctf_captimerecord)) - ctf_FakeTimeLimit(player, time + ctf_captimerecord); - } - - // effects - Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1); - - // waypoints - if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); } - ctf_FlagcarrierWaypoints(player); - WaypointSprite_Ping(player.wps_flagcarrier); -} - - -// =================== -// Main Flag Functions -// =================== - -void ctf_CheckFlagReturn(entity flag, int returntype) -{ - if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING)) - { - if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); } - - if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time)) - { - switch(returntype) - { - case RETURN_DROPPED: - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break; - case RETURN_DAMAGE: - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break; - case RETURN_SPEEDRUN: - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), TIME_ENCODE(ctf_captimerecord)); break; - case RETURN_NEEDKILL: - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break; - default: - case RETURN_TIMEOUT: - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break; - } - _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE); - ctf_EventLog("returned", flag.team, NULL); - flag.enemy = NULL; - ctf_RespawnFlag(flag); - } - } -} - -bool ctf_Stalemate_Customize(entity this, entity client) -{ - // make spectators see what the player would see - entity e = WaypointSprite_getviewentity(client); - entity wp_owner = this.owner; - - // team waypoints - //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; } - if(SAME_TEAM(wp_owner, e)) { return false; } - if(!IS_PLAYER(e)) { return false; } - - return true; -} - -void ctf_CheckStalemate() -{ - // declarations - int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0; - entity tmp_entity; - - entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs - - // build list of stale flags - for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) - { - if(autocvar_g_ctf_stalemate) - if(tmp_entity.ctf_status != FLAG_BASE) - if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag - { - tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist - ctf_staleflaglist = tmp_entity; - - switch(tmp_entity.team) - { - case NUM_TEAM_1: ++stale_red_flags; break; - case NUM_TEAM_2: ++stale_blue_flags; break; - case NUM_TEAM_3: ++stale_yellow_flags; break; - case NUM_TEAM_4: ++stale_pink_flags; break; - default: ++stale_neutral_flags; break; - } - } - } - - if(ctf_oneflag) - stale_flags = (stale_neutral_flags >= 1); - else - stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1); - - if(ctf_oneflag && stale_flags == 1) - ctf_stalemate = true; - else if(stale_flags >= 2) - ctf_stalemate = true; - else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2) - { ctf_stalemate = false; wpforenemy_announced = false; } - else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1) - { ctf_stalemate = false; wpforenemy_announced = false; } - - // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary - if(ctf_stalemate) - { - for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext) - { - if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier)) - { - entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, NULL, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG); - wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team); - setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize); - } - } - - if (!wpforenemy_announced) - { - FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((it.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER)); }); - - wpforenemy_announced = true; - } - } -} - -void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) -{ - if(ITEM_DAMAGE_NEEDKILL(deathtype)) - { - if(autocvar_g_ctf_flag_return_damage_delay) - this.ctf_flagdamaged_byworld = true; - else - { - this.health = 0; - ctf_CheckFlagReturn(this, RETURN_NEEDKILL); - } - return; - } - if(autocvar_g_ctf_flag_return_damage) - { - // reduce health and check if it should be returned - this.health = this.health - damage; - ctf_CheckFlagReturn(this, RETURN_DAMAGE); - return; - } -} - -void ctf_FlagThink(entity this) -{ - // declarations - entity tmp_entity; - - this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary. - - // captureshield - if(this == ctf_worldflaglist) // only for the first flag - FOREACH_CLIENT(true, { ctf_CaptureShield_Update(it, 1); }); // release shield only - - // sanity checks - if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished - LOG_TRACE("wtf the flag got squashed?"); - tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this); - if(!trace_startsolid || this.noalign) // can we resize it without getting stuck? - setsize(this, this.m_mins, this.m_maxs); - } - - // main think method - switch(this.ctf_status) - { - case FLAG_BASE: - { - if(autocvar_g_ctf_dropped_capture_radius) - { - for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) - if(tmp_entity.ctf_status == FLAG_DROPPED) - if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius)) - if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay) - ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED); - } - return; - } - - case FLAG_DROPPED: - { - this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it - - if(autocvar_g_ctf_flag_dropped_floatinwater) - { - vector midpoint = ((this.absmin + this.absmax) * 0.5); - if(pointcontents(midpoint) == CONTENT_WATER) - { - this.velocity = this.velocity * 0.5; - - if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER) - { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; } - else - { set_movetype(this, MOVETYPE_FLY); } - } - else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); } - } - if(autocvar_g_ctf_flag_return_dropped) - { - if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1)) - { - this.health = 0; - ctf_CheckFlagReturn(this, RETURN_DROPPED); - return; - } - } - if(this.ctf_flagdamaged_byworld) - { - this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE); - ctf_CheckFlagReturn(this, RETURN_NEEDKILL); - return; - } - else if(autocvar_g_ctf_flag_return_time) - { - this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE); - ctf_CheckFlagReturn(this, RETURN_TIMEOUT); - return; - } - return; - } - - case FLAG_CARRY: - { - if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord)) - { - this.health = 0; - ctf_CheckFlagReturn(this, RETURN_SPEEDRUN); - - CS(this.owner).impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set - ImpulseCommands(this.owner); - } - if(autocvar_g_ctf_stalemate) - { - if(time >= wpforenemy_nextthink) - { - ctf_CheckStalemate(); - wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check) - } - } - if(CTF_SAMETEAM(this, this.owner) && this.team) - { - if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed - ctf_Handle_Throw(this.owner, NULL, DROP_THROW); - else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius)) - ctf_Handle_Return(this, this.owner); - } - return; - } - - case FLAG_PASSING: - { - vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5); - targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us) - WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this); - - if((this.pass_target == NULL) - || (IS_DEAD(this.pass_target)) - || (this.pass_target.flagcarried) - || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius)) - || ((trace_fraction < 1) && (trace_ent != this.pass_target)) - || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit)) - { - // give up, pass failed - ctf_Handle_Drop(this, NULL, DROP_PASS); - } - else - { - // still a viable target, go for it - ctf_CalculatePassVelocity(this, targ_origin, this.origin, true); - } - return; - } - - default: // this should never happen - { - LOG_TRACE("ctf_FlagThink(): Flag exists with no status?"); - return; - } - } -} - -METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher)) -{ - return = false; - if(game_stopped) return; - if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; } - - bool is_not_monster = (!IS_MONSTER(toucher)); - - // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces - if(ITEM_TOUCH_NEEDKILL()) - { - if(!autocvar_g_ctf_flag_return_damage_delay) - { - flag.health = 0; - ctf_CheckFlagReturn(flag, RETURN_NEEDKILL); - } - if(!flag.ctf_flagdamaged_byworld) { return; } - } - - // special touch behaviors - if(STAT(FROZEN, toucher)) { return; } - else if(IS_VEHICLE(toucher)) - { - if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner) - toucher = toucher.owner; // the player is actually the vehicle owner, not other - else - return; // do nothing - } - else if(IS_MONSTER(toucher)) - { - if(!autocvar_g_ctf_allow_monster_touch) - return; // do nothing - } - else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world - { - if(time > flag.wait) // if we haven't in a while, play a sound/effect - { - Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1); - _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM); - flag.wait = time + FLAG_TOUCHRATE; - } - return; - } - else if(IS_DEAD(toucher)) { return; } - - switch(flag.ctf_status) - { - case FLAG_BASE: - { - if(ctf_oneflag) - { - if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster) - ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base - else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster) - ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag - } - else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster) - ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base - else if(CTF_DIFFTEAM(toucher, flag) && (toucher.flagcarried) && CTF_SAMETEAM(toucher.flagcarried, toucher) && (!toucher.ctf_captureshielded) && autocvar_g_ctf_flag_return_carrying && (time > toucher.next_take_time) && is_not_monster) - { - ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag - ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag - } - else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster) - ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag - break; - } - - case FLAG_DROPPED: - { - if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher)) - ctf_Handle_Return(flag, toucher); // toucher just returned his own flag - else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay))) - ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag - break; - } - - case FLAG_CARRY: - { - LOG_TRACE("Someone touched a flag even though it was being carried?"); - break; - } - - case FLAG_PASSING: - { - if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender)) - { - if(DIFF_TEAM(toucher, flag.pass_sender)) - { - if(ctf_Immediate_Return_Allowed(flag, toucher)) - ctf_Handle_Return(flag, toucher); - else if(is_not_monster && (!toucher.flagcarried)) - ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); - } - else if(!toucher.flagcarried) - ctf_Handle_Retrieve(flag, toucher); - } - break; - } - } -} - -.float last_respawn; -void ctf_RespawnFlag(entity flag) -{ - // check for flag respawn being called twice in a row - if(flag.last_respawn > time - 0.5) - { backtrace("flag respawn called twice quickly! please notify Samual about this..."); } - - flag.last_respawn = time; - - // reset the player (if there is one) - if((flag.owner) && (flag.owner.flagcarried == flag)) - { - WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier); - WaypointSprite_Kill(flag.owner.wps_flagreturn); - WaypointSprite_Kill(flag.wps_flagcarrier); - - flag.owner.flagcarried = NULL; - GameRules_scoring_vip(flag.owner, false); - - if(flag.speedrunning) - ctf_FakeTimeLimit(flag.owner, -1); - } - - if((flag.owner) && (flag.owner.vehicle)) - flag.scale = FLAG_SCALE; - - if(flag.ctf_status == FLAG_DROPPED) - { WaypointSprite_Kill(flag.wps_flagdropped); } - - // reset the flag - setattachment(flag, NULL, ""); - setorigin(flag, flag.ctf_spawnorigin); - - set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS)); - flag.takedamage = DAMAGE_NO; - flag.health = flag.max_flag_health; - flag.solid = SOLID_TRIGGER; - flag.velocity = '0 0 0'; - flag.angles = flag.mangle; - flag.flags = FL_ITEM | FL_NOTARGET; - - flag.ctf_status = FLAG_BASE; - flag.owner = NULL; - flag.pass_distance = 0; - flag.pass_sender = NULL; - flag.pass_target = NULL; - flag.ctf_dropper = NULL; - flag.ctf_pickuptime = 0; - flag.ctf_droptime = 0; - flag.ctf_flagdamaged_byworld = false; - navigation_dynamicgoal_unset(flag); - - ctf_CheckStalemate(); -} - -void ctf_Reset(entity this) -{ - if(this.owner && IS_PLAYER(this.owner)) - ctf_Handle_Throw(this.owner, NULL, DROP_RESET); - - this.enemy = NULL; - ctf_RespawnFlag(this); -} - -bool ctf_FlagBase_Customize(entity this, entity client) -{ - entity e = WaypointSprite_getviewentity(client); - entity wp_owner = this.owner; - entity flag = e.flagcarried; - if(flag && CTF_SAMETEAM(e, flag)) - return false; - if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt) - return false; - return true; -} - -void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup() -{ - // bot waypoints - waypoint_spawnforitem_force(this, this.origin); - navigation_dynamicgoal_init(this, true); - - // waypointsprites - entity basename; - switch (this.team) - { - case NUM_TEAM_1: basename = WP_FlagBaseRed; break; - case NUM_TEAM_2: basename = WP_FlagBaseBlue; break; - case NUM_TEAM_3: basename = WP_FlagBaseYellow; break; - case NUM_TEAM_4: basename = WP_FlagBasePink; break; - default: basename = WP_FlagBaseNeutral; break; - } - - entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG); - wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1'); - WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1')); - setcefc(wp, ctf_FlagBase_Customize); - - // captureshield setup - ctf_CaptureShield_Spawn(this); -} - -.bool pushable; - -void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc -{ - // main setup - flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist - ctf_worldflaglist = flag; - - setattachment(flag, NULL, ""); - - flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber))); - flag.team = teamnumber; - flag.classname = "item_flag_team"; - flag.target = "###item###"; // wut? - flag.flags = FL_ITEM | FL_NOTARGET; - IL_PUSH(g_items, flag); - flag.solid = SOLID_TRIGGER; - flag.takedamage = DAMAGE_NO; - flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale; - flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100); - flag.health = flag.max_flag_health; - flag.event_damage = ctf_FlagDamage; - flag.pushable = true; - flag.teleportable = TELEPORT_NORMAL; - flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP; - flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable; - flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable; - if(flag.damagedbycontents) - IL_PUSH(g_damagedbycontents, flag); - flag.velocity = '0 0 0'; - flag.mangle = flag.angles; - flag.reset = ctf_Reset; - settouch(flag, ctf_FlagTouch); - setthink(flag, ctf_FlagThink); - flag.nextthink = time + FLAG_THINKRATE; - flag.ctf_status = FLAG_BASE; - - // crudely force them all to 0 - if(autocvar_g_ctf_score_ignore_fields) - flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0; - - string teamname = Static_Team_ColorName_Lower(teamnumber); - // appearence - if(!flag.scale) { flag.scale = FLAG_SCALE; } - if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); } - if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); } - if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; } - if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; } - if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; } - - // sounds -#define X(s,b) \ - if(flag.s == "") flag.s = b; \ - precache_sound(flag.s); - - X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber)))) - X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber)))) - X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber)))) - X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber)))) - X(snd_flag_respawn, strzone(SND(CTF_RESPAWN))) - X(snd_flag_touch, strzone(SND(CTF_TOUCH))) - X(snd_flag_pass, strzone(SND(CTF_PASS))) -#undef X - - // precache - precache_model(flag.model); - - // appearence - _setmodel(flag, flag.model); // precision set below - setsize(flag, CTF_FLAG.m_mins * flag.scale, CTF_FLAG.m_maxs * flag.scale); - flag.m_mins = flag.mins; // store these for squash checks - flag.m_maxs = flag.maxs; - setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET)); - - if(autocvar_g_ctf_flag_glowtrails) - { - switch(teamnumber) - { - case NUM_TEAM_1: flag.glow_color = 251; break; - case NUM_TEAM_2: flag.glow_color = 210; break; - case NUM_TEAM_3: flag.glow_color = 110; break; - case NUM_TEAM_4: flag.glow_color = 145; break; - default: flag.glow_color = 254; break; - } - flag.glow_size = 25; - flag.glow_trail = 1; - } - - flag.effects |= EF_LOWPRECISION; - if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; } - if(autocvar_g_ctf_dynamiclights) - { - switch(teamnumber) - { - case NUM_TEAM_1: flag.effects |= EF_RED; break; - case NUM_TEAM_2: flag.effects |= EF_BLUE; break; - case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break; - case NUM_TEAM_4: flag.effects |= EF_RED; break; - default: flag.effects |= EF_DIMLIGHT; break; - } - } - - // flag placement - if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location - { - flag.dropped_origin = flag.origin; - flag.noalign = true; - set_movetype(flag, MOVETYPE_NONE); - } - else // drop to floor, automatically find a platform and set that as spawn origin - { - flag.noalign = false; - droptofloor(flag); - set_movetype(flag, MOVETYPE_NONE); - } - - InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION); -} - - -// ================ -// Bot player logic -// ================ - -// NOTE: LEGACY CODE, needs to be re-written! - -void havocbot_ctf_calculate_middlepoint() -{ - entity f; - vector s = '0 0 0'; - vector fo = '0 0 0'; - int n = 0; - - f = ctf_worldflaglist; - while (f) - { - fo = f.origin; - s = s + fo; - f = f.ctf_worldflagnext; - n++; - } - if(!n) - return; - - havocbot_middlepoint = s / n; - havocbot_middlepoint_radius = vlen(fo - havocbot_middlepoint); - - havocbot_symmetryaxis_equation = '0 0 0'; - if(n == 2) - { - // for symmetrical editing of waypoints - entity f1 = ctf_worldflaglist; - entity f2 = f1.ctf_worldflagnext; - float m = -(f1.origin.y - f2.origin.y) / (f1.origin.x - f2.origin.x); - float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x; - havocbot_symmetryaxis_equation.x = m; - havocbot_symmetryaxis_equation.y = q; - } - // store number of flags in this otherwise unused vector component - havocbot_symmetryaxis_equation.z = n; -} - - -entity havocbot_ctf_find_flag(entity bot) -{ - entity f; - f = ctf_worldflaglist; - while (f) - { - if (CTF_SAMETEAM(bot, f)) - return f; - f = f.ctf_worldflagnext; - } - return NULL; -} - -entity havocbot_ctf_find_enemy_flag(entity bot) -{ - entity f; - f = ctf_worldflaglist; - while (f) - { - if(ctf_oneflag) - { - if(CTF_DIFFTEAM(bot, f)) - { - if(f.team) - { - if(bot.flagcarried) - return f; - } - else if(!bot.flagcarried) - return f; - } - } - else if (CTF_DIFFTEAM(bot, f)) - return f; - f = f.ctf_worldflagnext; - } - return NULL; -} - -int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius) -{ - if (!teamplay) - return 0; - - int c = 0; - - FOREACH_CLIENT(IS_PLAYER(it), { - if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot) - continue; - - if(vdist(it.origin - org, <, tc_radius)) - ++c; - }); - - return c; -} - -// unused -#if 0 -void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale) -{ - entity head; - head = ctf_worldflaglist; - while (head) - { - if (CTF_SAMETEAM(this, head)) - break; - head = head.ctf_worldflagnext; - } - if (head) - navigation_routerating(this, head, ratingscale, 10000); -} -#endif - -void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale) -{ - entity head; - head = ctf_worldflaglist; - while (head) - { - if (CTF_SAMETEAM(this, head)) - { - if (this.flagcarried) - if ((this.flagcarried.cnt || head.cnt) && this.flagcarried.cnt != head.cnt) - { - head = head.ctf_worldflagnext; // skip base if it has a different group - continue; - } - break; - } - head = head.ctf_worldflagnext; - } - if (!head) - return; - - navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000); -} - -void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale) -{ - entity head; - head = ctf_worldflaglist; - while (head) - { - if(ctf_oneflag) - { - if(CTF_DIFFTEAM(this, head)) - { - if(head.team) - { - if(this.flagcarried) - break; - } - else if(!this.flagcarried) - break; - } - } - else if(CTF_DIFFTEAM(this, head)) - break; - head = head.ctf_worldflagnext; - } - if (head) - navigation_routerating(this, head, ratingscale, 10000); -} - -void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale) -{ - if (!bot_waypoints_for_items) - { - havocbot_goalrating_ctf_enemyflag(this, ratingscale); - return; - } - - entity head; - - head = havocbot_ctf_find_enemy_flag(this); - - if (!head) - return; - - navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000); -} - -void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale) -{ - entity mf; - - mf = havocbot_ctf_find_flag(this); - - if(mf.ctf_status == FLAG_BASE) - return; - - if(mf.tag_entity) - navigation_routerating(this, mf.tag_entity, ratingscale, 10000); -} - -void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius) -{ - entity head; - head = ctf_worldflaglist; - while (head) - { - // flag is out in the field - if(head.ctf_status != FLAG_BASE) - if(head.tag_entity==NULL) // dropped - { - if(df_radius) - { - if(vdist(org - head.origin, <, df_radius)) - navigation_routerating(this, head, ratingscale, 10000); - } - else - navigation_routerating(this, head, ratingscale, 10000); - } - - head = head.ctf_worldflagnext; - } -} - -void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius) -{ - IL_EACH(g_items, it.bot_pickup, - { - // gather health and armor only - if (it.solid) - if (it.health || it.armorvalue) - if (vdist(it.origin - org, <, sradius)) - { - // get the value of the item - float t = it.bot_pickupevalfunc(this, it) * 0.0001; - if (t > 0) - navigation_routerating(this, it, t * ratingscale, 500); - } - }); -} - -void havocbot_ctf_reset_role(entity this) -{ - float cdefense, cmiddle, coffense; - entity mf, ef; - float c; - - if(IS_DEAD(this)) - return; - - // Check ctf flags - if (this.flagcarried) - { - havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER); - return; - } - - mf = havocbot_ctf_find_flag(this); - ef = havocbot_ctf_find_enemy_flag(this); - - // Retrieve stolen flag - if(mf.ctf_status!=FLAG_BASE) - { - havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER); - return; - } - - // If enemy flag is taken go to the middle to intercept pursuers - if(ef.ctf_status!=FLAG_BASE) - { - havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE); - return; - } - - // if there is only me on the team switch to offense - c = 0; - FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), { ++c; }); - - if(c==1) - { - havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE); - return; - } - - // Evaluate best position to take - // Count mates on middle position - cmiddle = havocbot_ctf_teamcount(this, havocbot_middlepoint, havocbot_middlepoint_radius * 0.5); - - // Count mates on defense position - cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_middlepoint_radius * 0.5); - - // Count mates on offense position - coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_middlepoint_radius); - - if(cdefense<=coffense) - havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE); - else if(coffense<=cmiddle) - havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE); - else - havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE); -} - -void havocbot_role_ctf_carrier(entity this) -{ - if(IS_DEAD(this)) - { - havocbot_ctf_reset_role(this); - return; - } - - if (this.flagcarried == NULL) - { - havocbot_ctf_reset_role(this); - return; - } - - if (navigation_goalrating_timeout(this)) - { - navigation_goalrating_start(this); - - if(ctf_oneflag) - havocbot_goalrating_ctf_enemybase(this, 50000); - else - havocbot_goalrating_ctf_ourbase(this, 50000); - - if(this.health<100) - havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000); - - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - - entity head = ctf_worldflaglist; - while (head) - { - if (this.goalentity == head.bot_basewaypoint) - { - this.goalentity_lock_timeout = time + 5; - break; - } - head = head.ctf_worldflagnext; - } - - if (this.goalentity) - this.havocbot_cantfindflag = time + 10; - else if (time > this.havocbot_cantfindflag) - { - // Can't navigate to my own base, suicide! - // TODO: drop it and wander around - Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0'); - return; - } - } -} - -void havocbot_role_ctf_escort(entity this) -{ - entity mf, ef; - - if(IS_DEAD(this)) - { - havocbot_ctf_reset_role(this); - return; - } - - if (this.flagcarried) - { - havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER); - return; - } - - // If enemy flag is back on the base switch to previous role - ef = havocbot_ctf_find_enemy_flag(this); - if(ef.ctf_status==FLAG_BASE) - { - this.havocbot_role = this.havocbot_previous_role; - this.havocbot_role_timeout = 0; - return; - } - - // If the flag carrier reached the base switch to defense - mf = havocbot_ctf_find_flag(this); - if(mf.ctf_status!=FLAG_BASE) - if(vdist(ef.origin - mf.dropped_origin, <, 300)) - { - havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE); - return; - } - - // Set the role timeout if necessary - if (!this.havocbot_role_timeout) - { - this.havocbot_role_timeout = time + random() * 30 + 60; - } - - // If nothing happened just switch to previous role - if (time > this.havocbot_role_timeout) - { - this.havocbot_role = this.havocbot_previous_role; - this.havocbot_role_timeout = 0; - return; - } - - // Chase the flag carrier - if (navigation_goalrating_timeout(this)) - { - navigation_goalrating_start(this); - - havocbot_goalrating_ctf_enemyflag(this, 30000); - havocbot_goalrating_ctf_ourstolenflag(this, 40000); - havocbot_goalrating_items(this, 10000, this.origin, 10000); - - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - } -} - -void havocbot_role_ctf_offense(entity this) -{ - entity mf, ef; - vector pos; - - if(IS_DEAD(this)) - { - havocbot_ctf_reset_role(this); - return; - } - - if (this.flagcarried) - { - havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER); - return; - } - - // Check flags - mf = havocbot_ctf_find_flag(this); - ef = havocbot_ctf_find_enemy_flag(this); - - // Own flag stolen - if(mf.ctf_status!=FLAG_BASE) - { - if(mf.tag_entity) - pos = mf.tag_entity.origin; - else - pos = mf.origin; - - // Try to get it if closer than the enemy base - if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos)) - { - havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER); - return; - } - } - - // Escort flag carrier - if(ef.ctf_status!=FLAG_BASE) - { - if(ef.tag_entity) - pos = ef.tag_entity.origin; - else - pos = ef.origin; - - if(vdist(pos - mf.dropped_origin, >, 700)) - { - havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT); - return; - } - } - - // About to fail, switch to middlefield - if(this.health<50) - { - havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE); - return; - } - - // Set the role timeout if necessary - if (!this.havocbot_role_timeout) - this.havocbot_role_timeout = time + 120; - - if (time > this.havocbot_role_timeout) - { - havocbot_ctf_reset_role(this); - return; - } - - if (navigation_goalrating_timeout(this)) - { - navigation_goalrating_start(this); - - havocbot_goalrating_ctf_ourstolenflag(this, 50000); - havocbot_goalrating_ctf_enemybase(this, 20000); - havocbot_goalrating_items(this, 5000, this.origin, 1000); - havocbot_goalrating_items(this, 1000, this.origin, 10000); - - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - } -} - -// Retriever (temporary role): -void havocbot_role_ctf_retriever(entity this) -{ - entity mf; - - if(IS_DEAD(this)) - { - havocbot_ctf_reset_role(this); - return; - } - - if (this.flagcarried) - { - havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER); - return; - } - - // If flag is back on the base switch to previous role - mf = havocbot_ctf_find_flag(this); - if(mf.ctf_status==FLAG_BASE) - { - if (mf.enemy == this) // did this bot return the flag? - navigation_goalrating_timeout_force(this); - havocbot_ctf_reset_role(this); - return; - } - - if (!this.havocbot_role_timeout) - this.havocbot_role_timeout = time + 20; - - if (time > this.havocbot_role_timeout) - { - havocbot_ctf_reset_role(this); - return; - } - - if (navigation_goalrating_timeout(this)) - { - float rt_radius; - rt_radius = 10000; - - navigation_goalrating_start(this); - - havocbot_goalrating_ctf_ourstolenflag(this, 50000); - havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius); - havocbot_goalrating_ctf_enemybase(this, 30000); - havocbot_goalrating_items(this, 500, this.origin, rt_radius); - - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - } -} - -void havocbot_role_ctf_middle(entity this) -{ - entity mf; - - if(IS_DEAD(this)) - { - havocbot_ctf_reset_role(this); - return; - } - - if (this.flagcarried) - { - havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER); - return; - } - - mf = havocbot_ctf_find_flag(this); - if(mf.ctf_status!=FLAG_BASE) - { - havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER); - return; - } - - if (!this.havocbot_role_timeout) - this.havocbot_role_timeout = time + 10; - - if (time > this.havocbot_role_timeout) - { - havocbot_ctf_reset_role(this); - return; - } - - if (navigation_goalrating_timeout(this)) - { - vector org; - - org = havocbot_middlepoint; - org.z = this.origin.z; - - navigation_goalrating_start(this); - - havocbot_goalrating_ctf_ourstolenflag(this, 50000); - havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000); - havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5); - havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5); - havocbot_goalrating_items(this, 2500, this.origin, 10000); - havocbot_goalrating_ctf_enemybase(this, 2500); - - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - } -} - -void havocbot_role_ctf_defense(entity this) -{ - entity mf; - - if(IS_DEAD(this)) - { - havocbot_ctf_reset_role(this); - return; - } - - if (this.flagcarried) - { - havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER); - return; - } - - // If own flag was captured - mf = havocbot_ctf_find_flag(this); - if(mf.ctf_status!=FLAG_BASE) - { - havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER); - return; - } - - if (!this.havocbot_role_timeout) - this.havocbot_role_timeout = time + 30; - - if (time > this.havocbot_role_timeout) - { - havocbot_ctf_reset_role(this); - return; - } - if (navigation_goalrating_timeout(this)) - { - vector org = mf.dropped_origin; - - navigation_goalrating_start(this); - - // if enemies are closer to our base, go there - entity closestplayer = NULL; - float distance, bestdistance = 10000; - FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), { - distance = vlen(org - it.origin); - if(distance, 1000)) - if(checkpvs(this.origin,closestplayer)||random()<0.5) - havocbot_goalrating_ctf_ourbase(this, 30000); - - havocbot_goalrating_ctf_ourstolenflag(this, 20000); - havocbot_goalrating_ctf_droppedflags(this, 20000, org, havocbot_middlepoint_radius); - havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius); - havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius); - havocbot_goalrating_items(this, 5000, this.origin, 10000); - - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - } -} - -void havocbot_role_ctf_setrole(entity bot, int role) -{ - string s = "(null)"; - switch(role) - { - case HAVOCBOT_CTF_ROLE_CARRIER: - s = "carrier"; - bot.havocbot_role = havocbot_role_ctf_carrier; - bot.havocbot_role_timeout = 0; - bot.havocbot_cantfindflag = time + 10; - if (bot.havocbot_previous_role != bot.havocbot_role) - navigation_goalrating_timeout_force(bot); - break; - case HAVOCBOT_CTF_ROLE_DEFENSE: - s = "defense"; - bot.havocbot_role = havocbot_role_ctf_defense; - bot.havocbot_role_timeout = 0; - break; - case HAVOCBOT_CTF_ROLE_MIDDLE: - s = "middle"; - bot.havocbot_role = havocbot_role_ctf_middle; - bot.havocbot_role_timeout = 0; - break; - case HAVOCBOT_CTF_ROLE_OFFENSE: - s = "offense"; - bot.havocbot_role = havocbot_role_ctf_offense; - bot.havocbot_role_timeout = 0; - break; - case HAVOCBOT_CTF_ROLE_RETRIEVER: - s = "retriever"; - bot.havocbot_previous_role = bot.havocbot_role; - bot.havocbot_role = havocbot_role_ctf_retriever; - bot.havocbot_role_timeout = time + 10; - if (bot.havocbot_previous_role != bot.havocbot_role) - navigation_goalrating_timeout_expire(bot, 2); - break; - case HAVOCBOT_CTF_ROLE_ESCORT: - s = "escort"; - bot.havocbot_previous_role = bot.havocbot_role; - bot.havocbot_role = havocbot_role_ctf_escort; - bot.havocbot_role_timeout = time + 30; - if (bot.havocbot_previous_role != bot.havocbot_role) - navigation_goalrating_timeout_expire(bot, 2); - break; - } - LOG_TRACE(bot.netname, " switched to ", s); -} - - -// ============== -// Hook Functions -// ============== - -MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink) -{ - entity player = M_ARGV(0, entity); - - int t = 0, t2 = 0, t3 = 0; - bool b1 = false, b2 = false, b3 = false, b4 = false, b5 = false; // TODO: kill this, we WANT to show the other flags, somehow! (note: also means you don't see if you're FC) - - // initially clear items so they can be set as necessary later. - STAT(CTF_FLAGSTATUS, player) &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST - | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST - | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST - | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST - | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST - | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE); - - // scan through all the flags and notify the client about them - for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) - { - if(flag.team == NUM_TEAM_1 && !b1) { b1 = true; t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; } - if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; } - if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; } - if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; } - if(flag.team == 0 && !b5) { b5 = true; t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; STAT(CTF_FLAGSTATUS, player) |= CTF_FLAG_NEUTRAL; } - - switch(flag.ctf_status) - { - case FLAG_PASSING: - case FLAG_CARRY: - { - if((flag.owner == player) || (flag.pass_sender == player)) - STAT(CTF_FLAGSTATUS, player) |= t; // carrying: player is currently carrying the flag - else - STAT(CTF_FLAGSTATUS, player) |= t2; // taken: someone else is carrying the flag - break; - } - case FLAG_DROPPED: - { - STAT(CTF_FLAGSTATUS, player) |= t3; // lost: the flag is dropped somewhere on the map - break; - } - } - } - - // item for stopping players from capturing the flag too often - if(player.ctf_captureshielded) - STAT(CTF_FLAGSTATUS, player) |= CTF_SHIELDED; - - if(ctf_stalemate) - STAT(CTF_FLAGSTATUS, player) |= CTF_STALEMATE; - - // update the health of the flag carrier waypointsprite - if(player.wps_flagcarrier) - WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id)); -} - -MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc -{ - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - float frag_damage = M_ARGV(4, float); - vector frag_force = M_ARGV(6, vector); - - if(frag_attacker.flagcarried) // if the attacker is a flagcarrier - { - if(frag_target == frag_attacker) // damage done to yourself - { - frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor; - frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor; - } - else // damage done to everyone else - { - frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor; - frag_force *= autocvar_g_ctf_flagcarrier_forcefactor; - } - - M_ARGV(4, float) = frag_damage; - M_ARGV(6, vector) = frag_force; - } - else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier - { - if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id))) - if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time) - { - frag_target.wps_helpme_time = time; - WaypointSprite_HelpMePing(frag_target.wps_flagcarrier); - } - // todo: add notification for when flag carrier needs help? - } -} - -MUTATOR_HOOKFUNCTION(ctf, PlayerDies) -{ - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - - if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried)) - { - GameRules_scoring_add_team(frag_attacker, SCORE, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill)); - GameRules_scoring_add(frag_attacker, CTF_FCKILLS, 1); - } - - if(frag_target.flagcarried) - { - entity tmp_entity = frag_target.flagcarried; - ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL); - tmp_entity.ctf_dropper = NULL; - } -} - -MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill) -{ - M_ARGV(2, float) = 0; // frag score - return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true -} - -void ctf_RemovePlayer(entity player) -{ - if(player.flagcarried) - { ctf_Handle_Throw(player, NULL, DROP_NORMAL); } - - for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) - { - if(flag.pass_sender == player) { flag.pass_sender = NULL; } - if(flag.pass_target == player) { flag.pass_target = NULL; } - if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; } - } -} - -MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver) -{ - entity player = M_ARGV(0, entity); - - ctf_RemovePlayer(player); -} - -MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect) -{ - entity player = M_ARGV(0, entity); - - ctf_RemovePlayer(player); -} - -MUTATOR_HOOKFUNCTION(ctf, ClientConnect) -{ - if(!autocvar_g_ctf_leaderboard) - return; - - entity player = M_ARGV(0, entity); - - if(IS_REAL_CLIENT(player)) - { - for(int i = 1; i <= RANKINGS_CNT; ++i) - { - race_SendRankings(i, 0, 0, MSG_ONE); - } - } -} - -MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys) -{ - if(!autocvar_g_ctf_leaderboard) - return; - - entity player = M_ARGV(0, entity); - - if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1) - { - if (!player.stored_netname) - player.stored_netname = strzone(uid2name(player.crypto_idfp)); - if(player.stored_netname != player.netname) - { - db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname); - strcpy(player.stored_netname, player.netname); - } - } -} - -MUTATOR_HOOKFUNCTION(ctf, PortalTeleport) -{ - entity player = M_ARGV(0, entity); - - if(player.flagcarried) - if(!autocvar_g_ctf_portalteleport) - { ctf_Handle_Throw(player, NULL, DROP_NORMAL); } -} - -MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey) -{ - if(MUTATOR_RETURNVALUE || game_stopped) return; - - entity player = M_ARGV(0, entity); - - if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch)) - { - // pass the flag to a team mate - if(autocvar_g_ctf_pass) - { - entity head, closest_target = NULL; - head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true); - - while(head) // find the closest acceptable target to pass to - { - if(IS_PLAYER(head) && !IS_DEAD(head)) - if(head != player && SAME_TEAM(head, player)) - if(!head.speedrunning && !head.vehicle) - { - // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) - vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head)); - vector passer_center = CENTER_OR_VIEWOFS(player); - - if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest)) - { - if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried) - { - if(IS_BOT_CLIENT(head)) - { - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname); - ctf_Handle_Throw(head, player, DROP_PASS); - } - else - { - Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname); - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname); - } - player.throw_antispam = time + autocvar_g_ctf_pass_wait; - return true; - } - else if(player.flagcarried && !head.flagcarried) - { - if(closest_target) - { - vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target)); - if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center)) - { closest_target = head; } - } - else { closest_target = head; } - } - } - } - head = head.chain; - } - - if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; } - } - - // throw the flag in front of you - if(autocvar_g_ctf_throw && player.flagcarried) - { - if(player.throw_count == -1) - { - if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - { - player.throw_prevtime = time; - player.throw_count = 1; - ctf_Handle_Throw(player, NULL, DROP_THROW); - return true; - } - else - { - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time)); - return false; - } - } - else - { - if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; } - else { player.throw_count += 1; } - if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; } - - player.throw_prevtime = time; - ctf_Handle_Throw(player, NULL, DROP_THROW); - return true; - } - } - } -} - -MUTATOR_HOOKFUNCTION(ctf, HelpMePing) -{ - entity player = M_ARGV(0, entity); - - if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification - { - player.wps_helpme_time = time; - WaypointSprite_HelpMePing(player.wps_flagcarrier); - } - else // create a normal help me waypointsprite - { - WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME); - WaypointSprite_Ping(player.wps_helpme); - } - - return true; -} - -MUTATOR_HOOKFUNCTION(ctf, VehicleEnter) -{ - entity player = M_ARGV(0, entity); - entity veh = M_ARGV(1, entity); - - if(player.flagcarried) - { - if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch) - { - ctf_Handle_Throw(player, NULL, DROP_NORMAL); - } - else - { - player.flagcarried.nodrawtoclient = player; // hide the flag from the driver - setattachment(player.flagcarried, veh, ""); - setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET); - player.flagcarried.scale = VEHICLE_FLAG_SCALE; - //player.flagcarried.angles = '0 0 0'; - } - return true; - } -} - -MUTATOR_HOOKFUNCTION(ctf, VehicleExit) -{ - entity player = M_ARGV(0, entity); - - if(player.flagcarried) - { - setattachment(player.flagcarried, player, ""); - setorigin(player.flagcarried, FLAG_CARRY_OFFSET); - player.flagcarried.scale = FLAG_SCALE; - player.flagcarried.angles = '0 0 0'; - player.flagcarried.nodrawtoclient = NULL; - return true; - } -} - -MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun) -{ - entity player = M_ARGV(0, entity); - - if(player.flagcarried) - { - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN)); - ctf_RespawnFlag(player.flagcarried); - return true; - } -} - -MUTATOR_HOOKFUNCTION(ctf, MatchEnd) -{ - entity flag; // temporary entity for the search method - - for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) - { - switch(flag.ctf_status) - { - case FLAG_DROPPED: - case FLAG_PASSING: - { - // lock the flag, game is over - set_movetype(flag, MOVETYPE_NONE); - flag.takedamage = DAMAGE_NO; - flag.solid = SOLID_NOT; - flag.nextthink = false; // stop thinking - - //dprint("stopping the ", flag.netname, " from moving.\n"); - break; - } - - default: - case FLAG_BASE: - case FLAG_CARRY: - { - // do nothing for these flags - break; - } - } - } -} - -MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole) -{ - entity bot = M_ARGV(0, entity); - - havocbot_ctf_reset_role(bot); - return true; -} - -MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams) -{ - //M_ARGV(0, float) = ctf_teams; - M_ARGV(1, string) = "ctf_team"; - return true; -} - -MUTATOR_HOOKFUNCTION(ctf, SpectateCopy) -{ - entity spectatee = M_ARGV(0, entity); - entity client = M_ARGV(1, entity); - - STAT(CTF_FLAGSTATUS, client) = STAT(CTF_FLAGSTATUS, spectatee); -} - -MUTATOR_HOOKFUNCTION(ctf, GetRecords) -{ - int record_page = M_ARGV(0, int); - string ret_string = M_ARGV(1, string); - - for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i) - { - if (MapInfo_Get_ByID(i)) - { - float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time"))); - - if(!r) - continue; - - // TODO: uid2name - string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname")); - ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n"); - } - } - - M_ARGV(1, string) = ret_string; -} - -bool superspec_Spectate(entity this, entity targ); // TODO -void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO -MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand) -{ - entity player = M_ARGV(0, entity); - string cmd_name = M_ARGV(1, string); - int cmd_argc = M_ARGV(2, int); - - if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; } - - if(cmd_name == "followfc") - { - if(!g_ctf) - return true; - - int _team = 0; - bool found = false; - - if(cmd_argc == 2) - { - switch(argv(1)) - { - case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break; - case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break; - case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break; - case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break; - } - } - - FOREACH_CLIENT(IS_PLAYER(it), { - if(it.flagcarried && (it.team == _team || _team == 0)) - { - found = true; - if(_team == 0 && IS_SPEC(player) && player.enemy == it) - continue; // already spectating this fc, try another - return superspec_Spectate(player, it); - } - }); - - if(!found) - superspec_msg("", "", player, "No active flag carrier\n", 1); - return true; - } -} - -MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems) -{ - entity frag_target = M_ARGV(0, entity); - - if(frag_target.flagcarried) - ctf_Handle_Throw(frag_target, NULL, DROP_THROW); -} - - -// ========== -// Spawnfuncs -// ========== - -/*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37) -CTF flag for team one (Red). -Keys: -"angle" Angle the flag will point (minus 90 degrees)... -"model" model to use, note this needs red and blue as skins 0 and 1... -"noise" sound played when flag is picked up... -"noise1" sound played when flag is returned by a teammate... -"noise2" sound played when flag is captured... -"noise3" sound played when flag is lost in the field and respawns itself... -"noise4" sound played when flag is dropped by a player... -"noise5" sound played when flag touches the ground... */ -spawnfunc(item_flag_team1) -{ - if(!g_ctf) { delete(this); return; } - - ctf_FlagSetup(NUM_TEAM_1, this); -} - -/*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37) -CTF flag for team two (Blue). -Keys: -"angle" Angle the flag will point (minus 90 degrees)... -"model" model to use, note this needs red and blue as skins 0 and 1... -"noise" sound played when flag is picked up... -"noise1" sound played when flag is returned by a teammate... -"noise2" sound played when flag is captured... -"noise3" sound played when flag is lost in the field and respawns itself... -"noise4" sound played when flag is dropped by a player... -"noise5" sound played when flag touches the ground... */ -spawnfunc(item_flag_team2) -{ - if(!g_ctf) { delete(this); return; } - - ctf_FlagSetup(NUM_TEAM_2, this); -} - -/*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37) -CTF flag for team three (Yellow). -Keys: -"angle" Angle the flag will point (minus 90 degrees)... -"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3... -"noise" sound played when flag is picked up... -"noise1" sound played when flag is returned by a teammate... -"noise2" sound played when flag is captured... -"noise3" sound played when flag is lost in the field and respawns itself... -"noise4" sound played when flag is dropped by a player... -"noise5" sound played when flag touches the ground... */ -spawnfunc(item_flag_team3) -{ - if(!g_ctf) { delete(this); return; } - - ctf_FlagSetup(NUM_TEAM_3, this); -} - -/*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37) -CTF flag for team four (Pink). -Keys: -"angle" Angle the flag will point (minus 90 degrees)... -"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3... -"noise" sound played when flag is picked up... -"noise1" sound played when flag is returned by a teammate... -"noise2" sound played when flag is captured... -"noise3" sound played when flag is lost in the field and respawns itself... -"noise4" sound played when flag is dropped by a player... -"noise5" sound played when flag touches the ground... */ -spawnfunc(item_flag_team4) -{ - if(!g_ctf) { delete(this); return; } - - ctf_FlagSetup(NUM_TEAM_4, this); -} - -/*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37) -CTF flag (Neutral). -Keys: -"angle" Angle the flag will point (minus 90 degrees)... -"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3... -"noise" sound played when flag is picked up... -"noise1" sound played when flag is returned by a teammate... -"noise2" sound played when flag is captured... -"noise3" sound played when flag is lost in the field and respawns itself... -"noise4" sound played when flag is dropped by a player... -"noise5" sound played when flag touches the ground... */ -spawnfunc(item_flag_neutral) -{ - if(!g_ctf) { delete(this); return; } - if(!cvar("g_ctf_oneflag")) { delete(this); return; } - - ctf_FlagSetup(0, this); -} - -/*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32) -Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map. -Note: If you use spawnfunc_ctf_team entities you must define at least 2! However, unlike domination, you don't need to make a blank one too. -Keys: -"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)... -"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */ -spawnfunc(ctf_team) -{ - if(!g_ctf) { delete(this); return; } - - this.classname = "ctf_team"; - this.team = this.cnt + 1; -} - -// compatibility for quake maps -spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); } -spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); } -spawnfunc(info_player_team1); -spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); } -spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); } -spawnfunc(info_player_team2); -spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); } -spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); } - -spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); } -spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); } - -// compatibility for wop maps -spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); } -spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); } -spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); } -spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); } -spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); } -spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); } - - -// ============== -// Initialization -// ============== - -// scoreboard setup -void ctf_ScoreRules(int teams) -{ - CheckAllowedTeams(NULL); - GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, { - field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY); - field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); - field(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME); - field(SP_CTF_PICKUPS, "pickups", 0); - field(SP_CTF_FCKILLS, "fckills", 0); - field(SP_CTF_RETURNS, "returns", 0); - field(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER); - }); -} - -// code from here on is just to support maps that don't have flag and team entities -void ctf_SpawnTeam (string teamname, int teamcolor) -{ - entity this = new_pure(ctf_team); - this.netname = teamname; - this.cnt = teamcolor - 1; - this.spawnfunc_checked = true; - this.team = teamcolor; -} - -void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up. -{ - ctf_teams = 0; - - entity tmp_entity; - for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) - { - //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); } - //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); } - - switch(tmp_entity.team) - { - case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break; - case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break; - case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break; - case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break; - } - if(tmp_entity.team == 0) { ctf_oneflag = true; } - } - - havocbot_ctf_calculate_middlepoint(); - - if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags! - { - ctf_teams = 0; // so set the default red and blue teams - BITSET_ASSIGN(ctf_teams, BIT(0)); - BITSET_ASSIGN(ctf_teams, BIT(1)); - } - - //ctf_teams = bound(2, ctf_teams, 4); - - // if no teams are found, spawn defaults - if(find(NULL, classname, "ctf_team") == NULL) - { - LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway."); - if(ctf_teams & BIT(0)) - ctf_SpawnTeam("Red", NUM_TEAM_1); - if(ctf_teams & BIT(1)) - ctf_SpawnTeam("Blue", NUM_TEAM_2); - if(ctf_teams & BIT(2)) - ctf_SpawnTeam("Yellow", NUM_TEAM_3); - if(ctf_teams & BIT(3)) - ctf_SpawnTeam("Pink", NUM_TEAM_4); - } - - ctf_ScoreRules(ctf_teams); -} - -void ctf_Initialize() -{ - ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"))); - - ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore; - ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio; - ctf_captureshield_force = autocvar_g_ctf_shield_force; - - InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE); -} diff --git a/qcsrc/server/mutators/mutator/gamemode_ctf.qh b/qcsrc/server/mutators/mutator/gamemode_ctf.qh deleted file mode 100644 index 14bf281e7..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_ctf.qh +++ /dev/null @@ -1,190 +0,0 @@ -#pragma once - -#ifdef SVQC - -#include "../gamemode.qh" - -void ctf_Initialize(); - -REGISTER_MUTATOR(ctf, false) -{ - MUTATOR_STATIC(); - MUTATOR_ONADD - { - GameRules_teams(true); - GameRules_limit_score(autocvar_capturelimit_override); - GameRules_limit_lead(autocvar_captureleadlimit_override); - - ctf_Initialize(); - } - return 0; -} - -// used in cheats.qc -void ctf_RespawnFlag(entity flag); - -// score rule declarations -const int ST_CTF_CAPS = 1; - -CLASS(Flag, Pickup) - ATTRIB(Flag, m_mins, vector, (PL_MIN_CONST + '0 0 -13') * 1.4); // scaling be damned - ATTRIB(Flag, m_maxs, vector, (PL_MAX_CONST + '0 0 -13') * 1.4); -ENDCLASS(Flag) -Flag CTF_FLAG; STATIC_INIT(Flag) { CTF_FLAG = NEW(Flag); } -void ctf_FlagTouch(entity this, entity toucher) { ITEM_HANDLE(Pickup, CTF_FLAG, this, toucher); } - -// flag constants // for most of these, there is just one question to be asked: WHYYYYY? - -const float FLAG_SCALE = 0.6; - -const float FLAG_THINKRATE = 0.2; -const float FLAG_TOUCHRATE = 0.5; -const float WPFE_THINKRATE = 0.5; - -const vector FLAG_DROP_OFFSET = ('0 0 32'); -const vector FLAG_CARRY_OFFSET = ('-16 0 8'); -#define FLAG_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13)) -const vector FLAG_WAYPOINT_OFFSET = ('0 0 64'); -const vector FLAG_FLOAT_OFFSET = ('0 0 32'); -const vector FLAG_PASS_ARC_OFFSET = ('0 0 -10'); - -const vector VEHICLE_FLAG_OFFSET = ('0 0 96'); -const float VEHICLE_FLAG_SCALE = 1.0; - -// waypoint colors -#define WPCOLOR_ENEMYFC(t) ((t) ? colormapPaletteColor(t - 1, false) * 0.75 : '1 1 1') -#define WPCOLOR_FLAGCARRIER(t) (WP_FlagCarrier.m_color) -#define WPCOLOR_DROPPEDFLAG(t) ((t) ? ('0.25 0.25 0.25' + colormapPaletteColor(t - 1, false)) * 0.5 : '1 1 1') - -// sounds -#define snd_flag_taken noise -#define snd_flag_returned noise1 -#define snd_flag_capture noise2 -#define snd_flag_respawn noise3 -.string snd_flag_dropped; -.string snd_flag_touch; -.string snd_flag_pass; - -// score fields -.float score_assist; -.float score_capture; -.float score_drop; // note: negated -.float score_pickup; -.float score_return; -.float score_team_capture; // shouldn't be too high - -// effects -.string toucheffect; -.string passeffect; -.string capeffect; - -// list of flags on the map -entity ctf_worldflaglist; -.entity ctf_worldflagnext; -.entity ctf_staleflagnext; - -// waypoint sprites -.entity wps_helpme; -.entity wps_flagbase; -.entity wps_flagcarrier; -.entity wps_flagdropped; -.entity wps_flagreturn; -.entity wps_enemyflagcarrier; -.float wps_helpme_time; -bool wpforenemy_announced; -float wpforenemy_nextthink; - -// statuses -const int FLAG_BASE = 1; -const int FLAG_DROPPED = 2; -const int FLAG_CARRY = 3; -const int FLAG_PASSING = 4; - -const int DROP_NORMAL = 1; -const int DROP_THROW = 2; -const int DROP_PASS = 3; -const int DROP_RESET = 4; - -const int PICKUP_BASE = 1; -const int PICKUP_DROPPED = 2; - -const int CAPTURE_NORMAL = 1; -const int CAPTURE_DROPPED = 2; - -const int RETURN_TIMEOUT = 1; -const int RETURN_DROPPED = 2; -const int RETURN_DAMAGE = 3; -const int RETURN_SPEEDRUN = 4; -const int RETURN_NEEDKILL = 5; - -bool ctf_Stalemate_Customize(entity this, entity client); - -void ctf_Handle_Throw(entity player, entity receiver, float droptype); - -// flag properties -#define ctf_spawnorigin dropped_origin -bool ctf_stalemate; // indicates that a stalemate is active -float ctf_captimerecord; // record time for capturing the flag -.float ctf_pickuptime; -.float ctf_droptime; -.int ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally) -.entity ctf_dropper; // don't allow spam of dropping the flag -.int max_flag_health; -.float next_take_time; -.bool ctf_flagdamaged_byworld; -int ctf_teams; -.entity enemy; // when flag is back in the base, it remembers last player who carried/touched the flag, useful to bots - -// passing/throwing properties -.float pass_distance; -.entity pass_sender; -.entity pass_target; -.float throw_antispam; -.float throw_prevtime; -.int throw_count; - -// CaptureShield: If the player is too bad to be allowed to capture, shield them from taking the flag. -.bool ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture -float ctf_captureshield_min_negscore; // punish at -20 points -float ctf_captureshield_max_ratio; // punish at most 30% of each team -float ctf_captureshield_force; // push force of the shield - -// 1 flag ctf -bool ctf_oneflag; // indicates whether or not a neutral flag has been found - -// bot player logic -const int HAVOCBOT_CTF_ROLE_NONE = 0; -const int HAVOCBOT_CTF_ROLE_DEFENSE = 2; -const int HAVOCBOT_CTF_ROLE_MIDDLE = 4; -const int HAVOCBOT_CTF_ROLE_OFFENSE = 8; -const int HAVOCBOT_CTF_ROLE_CARRIER = 16; -const int HAVOCBOT_CTF_ROLE_RETRIEVER = 32; -const int HAVOCBOT_CTF_ROLE_ESCORT = 64; - -.bool havocbot_cantfindflag; - -void havocbot_role_ctf_setrole(entity bot, int role); - -// team checking -#define CTF_SAMETEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? DIFF_TEAM(a,b) : SAME_TEAM(a,b)) -#define CTF_DIFFTEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? SAME_TEAM(a,b) : DIFF_TEAM(a,b)) -#endif - -const int CTF_RED_FLAG_TAKEN = 1; -const int CTF_RED_FLAG_LOST = 2; -const int CTF_RED_FLAG_CARRYING = 3; -const int CTF_BLUE_FLAG_TAKEN = 4; -const int CTF_BLUE_FLAG_LOST = 8; -const int CTF_BLUE_FLAG_CARRYING = 12; -const int CTF_YELLOW_FLAG_TAKEN = 16; -const int CTF_YELLOW_FLAG_LOST = 32; -const int CTF_YELLOW_FLAG_CARRYING = 48; -const int CTF_PINK_FLAG_TAKEN = 64; -const int CTF_PINK_FLAG_LOST = 128; -const int CTF_PINK_FLAG_CARRYING = 192; -const int CTF_NEUTRAL_FLAG_TAKEN = 256; -const int CTF_NEUTRAL_FLAG_LOST = 512; -const int CTF_NEUTRAL_FLAG_CARRYING = 768; -const int CTF_FLAG_NEUTRAL = 2048; -const int CTF_SHIELDED = 4096; -const int CTF_STALEMATE = 8192; diff --git a/qcsrc/server/mutators/mutator/gamemode_cts.qc b/qcsrc/server/mutators/mutator/gamemode_cts.qc deleted file mode 100644 index 1363411aa..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_cts.qc +++ /dev/null @@ -1,430 +0,0 @@ -#include "gamemode_cts.qh" - -#include -#include - -float autocvar_g_cts_finish_kill_delay; -bool autocvar_g_cts_selfdamage; - -// legacy bot roles -.float race_checkpoint; -void havocbot_role_cts(entity this) -{ - if(IS_DEAD(this)) - return; - - if (navigation_goalrating_timeout(this)) - { - navigation_goalrating_start(this); - - bool raw_touch_check = true; - int cp = this.race_checkpoint; - - LABEL(search_racecheckpoints) - IL_EACH(g_racecheckpoints, true, - { - if(it.cnt == cp || cp == -1) - { - // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint - // e.g. checkpoint in front of Stormkeep's warpzone - // the same workaround is applied in Race game mode - if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30)) - { - cp = race_NextCheckpoint(cp); - raw_touch_check = false; - goto search_racecheckpoints; - } - navigation_routerating(this, it, 1000000, 5000); - } - }); - - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - } -} - -void cts_ScoreRules() -{ - GameRules_score_enabled(false); - GameRules_scoring(0, 0, 0, { - if (g_race_qualifying) { - field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME); - } else { - field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY); - field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME); - field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME); - } - }); -} - -void cts_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later -{ - if(autocvar_sv_eventlog) - GameLogEcho(strcat(":cts:", mode, ":", ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : ""))); -} - -void KillIndicator_Think(entity this); -void CTS_ClientKill(entity e) // silent version of ClientKill, used when player finishes a CTS run. Useful to prevent cheating by running back to the start line and starting out with more speed -{ - e.killindicator = spawn(); - e.killindicator.owner = e; - setthink(e.killindicator, KillIndicator_Think); - e.killindicator.nextthink = time + (e.lip) * 0.05; - e.killindicator.cnt = ceil(autocvar_g_cts_finish_kill_delay); - e.killindicator.health = 1; // this is used to indicate that it should be silent - e.lip = 0; -} - -MUTATOR_HOOKFUNCTION(cts, PlayerPhysics) -{ - entity player = M_ARGV(0, entity); - float dt = M_ARGV(1, float); - - player.race_movetime_frac += dt; - float f = floor(player.race_movetime_frac); - player.race_movetime_frac -= f; - player.race_movetime_count += f; - player.race_movetime = player.race_movetime_frac + player.race_movetime_count; - -#ifdef SVQC - if(IS_PLAYER(player)) - { - if (player.race_penalty) - if (time > player.race_penalty) - player.race_penalty = 0; - if(player.race_penalty) - { - player.velocity = '0 0 0'; - set_movetype(player, MOVETYPE_NONE); - player.disableclientprediction = 2; - } - } -#endif - - // force kbd movement for fairness - float wishspeed; - vector wishvel; - - // if record times matter - // ensure nothing EVIL is being done (i.e. div0_evade) - // this hinders joystick users though - // but it still gives SOME analog control - wishvel.x = fabs(CS(player).movement.x); - wishvel.y = fabs(CS(player).movement.y); - if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y) - { - wishvel.z = 0; - wishspeed = vlen(wishvel); - if(wishvel.x >= 2 * wishvel.y) - { - // pure X motion - if(CS(player).movement.x > 0) - CS(player).movement_x = wishspeed; - else - CS(player).movement_x = -wishspeed; - CS(player).movement_y = 0; - } - else if(wishvel.y >= 2 * wishvel.x) - { - // pure Y motion - CS(player).movement_x = 0; - if(CS(player).movement.y > 0) - CS(player).movement_y = wishspeed; - else - CS(player).movement_y = -wishspeed; - } - else - { - // diagonal - if(CS(player).movement.x > 0) - CS(player).movement_x = M_SQRT1_2 * wishspeed; - else - CS(player).movement_x = -M_SQRT1_2 * wishspeed; - if(CS(player).movement.y > 0) - CS(player).movement_y = M_SQRT1_2 * wishspeed; - else - CS(player).movement_y = -M_SQRT1_2 * wishspeed; - } - } -} - -MUTATOR_HOOKFUNCTION(cts, reset_map_global) -{ - float s; - - Score_NicePrint(NULL); - - race_ClearRecords(); - PlayerScore_Sort(race_place, 0, 1, 0); - - FOREACH_CLIENT(true, { - if(it.race_place) - { - s = GameRules_scoring_add(it, RACE_FASTEST, 0); - if(!s) - it.race_place = 0; - } - cts_EventLog(ftos(it.race_place), it); - }); - - if(g_race_qualifying == 2) - { - g_race_qualifying = 0; - independent_players = 0; - cvar_set("fraglimit", ftos(race_fraglimit)); - cvar_set("leadlimit", ftos(race_leadlimit)); - cvar_set("timelimit", ftos(race_timelimit)); - cts_ScoreRules(); - } -} - -MUTATOR_HOOKFUNCTION(cts, ClientConnect) -{ - entity player = M_ARGV(0, entity); - - race_PreparePlayer(player); - player.race_checkpoint = -1; - - if(IS_REAL_CLIENT(player)) - { - string rr = CTS_RECORD; - - msg_entity = player; - race_send_recordtime(MSG_ONE); - race_send_speedaward(MSG_ONE); - - speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"))); - speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"))); - race_send_speedaward_alltimebest(MSG_ONE); - - float i; - for (i = 1; i <= RANKINGS_CNT; ++i) - { - race_SendRankings(i, 0, 0, MSG_ONE); - } - } -} - -MUTATOR_HOOKFUNCTION(cts, AbortSpeedrun) -{ - entity player = M_ARGV(0, entity); - - if(autocvar_g_allow_checkpoints) - race_PreparePlayer(player); // nice try -} - -MUTATOR_HOOKFUNCTION(cts, MakePlayerObserver) -{ - entity player = M_ARGV(0, entity); - - if(GameRules_scoring_add(player, RACE_FASTEST, 0)) - player.frags = FRAGS_LMS_LOSER; - else - player.frags = FRAGS_SPECTATOR; - - race_PreparePlayer(player); - player.race_checkpoint = -1; -} - -MUTATOR_HOOKFUNCTION(cts, PlayerSpawn) -{ - entity player = M_ARGV(0, entity); - entity spawn_spot = M_ARGV(1, entity); - - if(spawn_spot.target == "") - // Emergency: this wasn't a real spawnpoint. Can this ever happen? - race_PreparePlayer(player); - - // if we need to respawn, do it right - player.race_respawn_checkpoint = player.race_checkpoint; - player.race_respawn_spotref = spawn_spot; - - player.race_place = 0; -} - -MUTATOR_HOOKFUNCTION(cts, PutClientInServer) -{ - entity player = M_ARGV(0, entity); - - if(IS_PLAYER(player)) - if(!game_stopped) - { - if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn - race_PreparePlayer(player); - else // respawn - race_RetractPlayer(player); - - race_AbandonRaceCheck(player); - } -} - -MUTATOR_HOOKFUNCTION(cts, PlayerDies) -{ - entity frag_target = M_ARGV(2, entity); - - frag_target.respawn_flags |= RESPAWN_FORCE; - race_AbandonRaceCheck(frag_target); -} - -MUTATOR_HOOKFUNCTION(cts, HavocBot_ChooseRole) -{ - entity bot = M_ARGV(0, entity); - - bot.havocbot_role = havocbot_role_cts; - return true; -} - -MUTATOR_HOOKFUNCTION(cts, GetPressedKeys) -{ - entity player = M_ARGV(0, entity); - - if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1) - { - if (!player.stored_netname) - player.stored_netname = strzone(uid2name(player.crypto_idfp)); - if(player.stored_netname != player.netname) - { - db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname); - strcpy(player.stored_netname, player.netname); - } - } - - if (!IS_OBSERVER(player)) - { - if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed)) - { - speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1'); - speedaward_holder = player.netname; - speedaward_uid = player.crypto_idfp; - speedaward_lastupdate = time; - } - if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1) - { - string rr = CTS_RECORD; - race_send_speedaward(MSG_ALL); - speedaward_lastsent = speedaward_speed; - if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "") - { - speedaward_alltimebest = speedaward_speed; - speedaward_alltimebest_holder = speedaward_holder; - speedaward_alltimebest_uid = speedaward_uid; - db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest)); - db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid); - race_send_speedaward_alltimebest(MSG_ALL); - } - } - } -} - -MUTATOR_HOOKFUNCTION(cts, ForbidThrowCurrentWeapon) -{ - // no weapon dropping in CTS - return true; -} - -MUTATOR_HOOKFUNCTION(cts, FilterItem) -{ - entity item = M_ARGV(0, entity); - - if (Item_IsLoot(item)) - { - return true; - } -} - -MUTATOR_HOOKFUNCTION(cts, Damage_Calculate) -{ - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - float frag_deathtype = M_ARGV(3, float); - float frag_damage = M_ARGV(4, float); - - if(frag_target == frag_attacker || frag_deathtype == DEATH_FALL.m_id) - if(!autocvar_g_cts_selfdamage) - { - frag_damage = 0; - M_ARGV(4, float) = frag_damage; - } -} - -MUTATOR_HOOKFUNCTION(cts, ForbidPlayerScore_Clear) -{ - return true; // in CTS, you don't lose score by observing -} - -MUTATOR_HOOKFUNCTION(cts, GetRecords) -{ - int record_page = M_ARGV(0, int); - string ret_string = M_ARGV(1, string); - - for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i) - { - if(MapInfo_Get_ByID(i)) - { - float r = race_readTime(MapInfo_Map_bspname, 1); - - if(!r) - continue; - - string h = race_readName(MapInfo_Map_bspname, 1); - ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n"); - } - } - - M_ARGV(1, string) = ret_string; -} - -void ClientKill_Now(entity this); -MUTATOR_HOOKFUNCTION(cts, ClientKill) -{ - entity player = M_ARGV(0, entity); - - M_ARGV(1, float) = 0; // kill delay - - if(player.killindicator && player.killindicator.health == 1) // player.killindicator.health == 1 means that the kill indicator was spawned by CTS_ClientKill - { - delete(player.killindicator); - player.killindicator = NULL; - - ClientKill_Now(player); // allow instant kill in this case - return; - } -} - -MUTATOR_HOOKFUNCTION(cts, Race_FinalCheckpoint) -{ - entity player = M_ARGV(0, entity); - - if(autocvar_g_cts_finish_kill_delay) - CTS_ClientKill(player); -} - -MUTATOR_HOOKFUNCTION(cts, HideTeamNagger) -{ - return true; // doesn't work so well (but isn't cts a teamless mode?) -} - -MUTATOR_HOOKFUNCTION(cts, FixClientCvars) -{ - entity player = M_ARGV(0, entity); - - stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n"); -} - -MUTATOR_HOOKFUNCTION(cts, WantWeapon) -{ - M_ARGV(1, float) = (M_ARGV(0, entity) == WEP_SHOTGUN); // want weapon = weapon info - M_ARGV(3, bool) = true; // want mutator blocked - return true; -} - -MUTATOR_HOOKFUNCTION(cts, ForbidDropCurrentWeapon) -{ - return true; -} - -void cts_Initialize() -{ - cts_ScoreRules(); -} diff --git a/qcsrc/server/mutators/mutator/gamemode_cts.qh b/qcsrc/server/mutators/mutator/gamemode_cts.qh deleted file mode 100644 index c90919e6f..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_cts.qh +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include "../gamemode.qh" -#include - -void cts_Initialize(); - -REGISTER_MUTATOR(cts, false) -{ - MUTATOR_STATIC(); - MUTATOR_ONADD - { - g_race_qualifying = true; - independent_players = 1; - GameRules_limit_score(0); - GameRules_limit_lead(0); - - cts_Initialize(); - } - return 0; -} - -// scores -const float ST_CTS_LAPS = 1; diff --git a/qcsrc/server/mutators/mutator/gamemode_deathmatch.qc b/qcsrc/server/mutators/mutator/gamemode_deathmatch.qc deleted file mode 100644 index 9590027d3..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_deathmatch.qc +++ /dev/null @@ -1,7 +0,0 @@ -#include "gamemode_deathmatch.qh" - -MUTATOR_HOOKFUNCTION(dm, Scores_CountFragsRemaining) -{ - // announce remaining frags - return true; -} diff --git a/qcsrc/server/mutators/mutator/gamemode_deathmatch.qh b/qcsrc/server/mutators/mutator/gamemode_deathmatch.qh deleted file mode 100644 index f45b0800f..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_deathmatch.qh +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "../gamemode.qh" - -REGISTER_MUTATOR(dm, false) -{ - MUTATOR_STATIC(); - return 0; -} diff --git a/qcsrc/server/mutators/mutator/gamemode_domination.qc b/qcsrc/server/mutators/mutator/gamemode_domination.qc deleted file mode 100644 index 38ef58b6c..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_domination.qc +++ /dev/null @@ -1,670 +0,0 @@ -#include "gamemode_domination.qh" - -#include - -bool g_domination; - -int autocvar_g_domination_default_teams; -bool autocvar_g_domination_disable_frags; -int autocvar_g_domination_point_amt; -bool autocvar_g_domination_point_fullbright; -float autocvar_g_domination_round_timelimit; -float autocvar_g_domination_warmup; -float autocvar_g_domination_point_rate; -int autocvar_g_domination_teams_override; - -void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later -{ - if(autocvar_sv_eventlog) - GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : ""))); -} - -void set_dom_state(entity e) -{ - STAT(DOM_TOTAL_PPS, e) = total_pps; - STAT(DOM_PPS_RED, e) = pps_red; - STAT(DOM_PPS_BLUE, e) = pps_blue; - if(domination_teams >= 3) - STAT(DOM_PPS_YELLOW, e) = pps_yellow; - if(domination_teams >= 4) - STAT(DOM_PPS_PINK, e) = pps_pink; -} - -void dompoint_captured(entity this) -{ - float old_delay, old_team, real_team; - - // now that the delay has expired, switch to the latest team to lay claim to this point - entity head = this.owner; - - real_team = this.cnt; - this.cnt = -1; - - dom_EventLog("taken", this.team, this.dmg_inflictor); - this.dmg_inflictor = NULL; - - this.goalentity = head; - this.model = head.mdl; - this.modelindex = head.dmg; - this.skin = head.skin; - - float points, wait_time; - if (autocvar_g_domination_point_amt) - points = autocvar_g_domination_point_amt; - else - points = this.frags; - if (autocvar_g_domination_point_rate) - wait_time = autocvar_g_domination_point_rate; - else - wait_time = this.wait; - - if(domination_roundbased) - bprint(sprintf("^3%s^3%s\n", head.netname, this.message)); - else - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time); - - if(this.enemy.playerid == this.enemy_playerid) - GameRules_scoring_add(this.enemy, DOM_TAKES, 1); - else - this.enemy = NULL; - - if (head.noise != "") - if(this.enemy) - _sound(this.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM); - else - _sound(this, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM); - if (head.noise1 != "") - play2all(head.noise1); - - this.delay = time + wait_time; - - // do trigger work - old_delay = this.delay; - old_team = this.team; - this.team = real_team; - this.delay = 0; - SUB_UseTargets (this, this, NULL); - this.delay = old_delay; - this.team = old_team; - - entity msg = WP_DomNeut; - switch(real_team) - { - case NUM_TEAM_1: msg = WP_DomRed; break; - case NUM_TEAM_2: msg = WP_DomBlue; break; - case NUM_TEAM_3: msg = WP_DomYellow; break; - case NUM_TEAM_4: msg = WP_DomPink; break; - } - - WaypointSprite_UpdateSprites(this.sprite, msg, WP_Null, WP_Null); - - total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0; - IL_EACH(g_dompoints, true, - { - if (autocvar_g_domination_point_amt) - points = autocvar_g_domination_point_amt; - else - points = it.frags; - if (autocvar_g_domination_point_rate) - wait_time = autocvar_g_domination_point_rate; - else - wait_time = it.wait; - switch(it.goalentity.team) - { - case NUM_TEAM_1: pps_red += points/wait_time; break; - case NUM_TEAM_2: pps_blue += points/wait_time; break; - case NUM_TEAM_3: pps_yellow += points/wait_time; break; - case NUM_TEAM_4: pps_pink += points/wait_time; break; - } - total_pps += points/wait_time; - }); - - WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0)); - WaypointSprite_Ping(this.sprite); - - this.captime = time; - - FOREACH_CLIENT(IS_REAL_CLIENT(it), { set_dom_state(it); }); -} - -void AnimateDomPoint(entity this) -{ - if(this.pain_finished > time) - return; - this.pain_finished = time + this.t_width; - if(this.nextthink > this.pain_finished) - this.nextthink = this.pain_finished; - - this.frame = this.frame + 1; - if(this.frame > this.t_length) - this.frame = 0; -} - -void dompointthink(entity this) -{ - float fragamt; - - this.nextthink = time + 0.1; - - //this.frame = this.frame + 1; - //if(this.frame > 119) - // this.frame = 0; - AnimateDomPoint(this); - - // give points - - if (game_stopped || this.delay > time || time < game_starttime) // game has ended, don't keep giving points - return; - - if(autocvar_g_domination_point_rate) - this.delay = time + autocvar_g_domination_point_rate; - else - this.delay = time + this.wait; - - // give credit to the team - // NOTE: this defaults to 0 - if (!domination_roundbased) - if (this.goalentity.netname != "") - { - if(autocvar_g_domination_point_amt) - fragamt = autocvar_g_domination_point_amt; - else - fragamt = this.frags; - TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt); - TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt); - - // give credit to the individual player, if he is still there - if (this.enemy.playerid == this.enemy_playerid) - { - GameRules_scoring_add(this.enemy, SCORE, fragamt); - GameRules_scoring_add(this.enemy, DOM_TICKS, fragamt); - } - else - this.enemy = NULL; - } -} - -void dompointtouch(entity this, entity toucher) -{ - if (!IS_PLAYER(toucher)) - return; - if (toucher.health < 1) - return; - - if(round_handler_IsActive() && !round_handler_IsRoundStarted()) - return; - - if(time < this.captime + 0.3) - return; - - // only valid teams can claim it - entity head = find(NULL, classname, "dom_team"); - while (head && head.team != toucher.team) - head = find(head, classname, "dom_team"); - if (!head || head.netname == "" || head == this.goalentity) - return; - - // delay capture - - this.team = this.goalentity.team; // this stores the PREVIOUS team! - - this.cnt = toucher.team; - this.owner = head; // team to switch to after the delay - this.dmg_inflictor = toucher; - - // this.state = 1; - // this.delay = time + cvar("g_domination_point_capturetime"); - //this.nextthink = time + cvar("g_domination_point_capturetime"); - //this.think = dompoint_captured; - - // go to neutral team in the mean time - head = find(NULL, classname, "dom_team"); - while (head && head.netname != "") - head = find(head, classname, "dom_team"); - if(head == NULL) - return; - - WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null); - WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1'); - WaypointSprite_Ping(this.sprite); - - this.goalentity = head; - this.model = head.mdl; - this.modelindex = head.dmg; - this.skin = head.skin; - - this.enemy = toucher; // individual player scoring - this.enemy_playerid = toucher.playerid; - dompoint_captured(this); -} - -void dom_controlpoint_setup(entity this) -{ - entity head; - // find the spawnfunc_dom_team representing unclaimed points - head = find(NULL, classname, "dom_team"); - while(head && head.netname != "") - head = find(head, classname, "dom_team"); - if (!head) - objerror(this, "no spawnfunc_dom_team with netname \"\" found\n"); - - // copy important properties from spawnfunc_dom_team entity - this.goalentity = head; - _setmodel(this, head.mdl); // precision already set - this.skin = head.skin; - - this.cnt = -1; - - if(this.message == "") - this.message = " has captured a control point"; - - if(this.frags <= 0) - this.frags = 1; - if(this.wait <= 0) - this.wait = 5; - - float points, waittime; - if (autocvar_g_domination_point_amt) - points = autocvar_g_domination_point_amt; - else - points = this.frags; - if (autocvar_g_domination_point_rate) - waittime = autocvar_g_domination_point_rate; - else - waittime = this.wait; - - total_pps += points/waittime; - - if(!this.t_width) - this.t_width = 0.02; // frame animation rate - if(!this.t_length) - this.t_length = 239; // maximum frame - - setthink(this, dompointthink); - this.nextthink = time; - settouch(this, dompointtouch); - this.solid = SOLID_TRIGGER; - if(!this.flags & FL_ITEM) - IL_PUSH(g_items, this); - this.flags = FL_ITEM; - setsize(this, '-32 -32 -32', '32 32 32'); - setorigin(this, this.origin + '0 0 20'); - droptofloor(this); - - waypoint_spawnforitem(this); - WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT); -} - -float total_controlpoints; -void Domination_count_controlpoints() -{ - total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0; - IL_EACH(g_dompoints, true, - { - ++total_controlpoints; - redowned += (it.goalentity.team == NUM_TEAM_1); - blueowned += (it.goalentity.team == NUM_TEAM_2); - yellowowned += (it.goalentity.team == NUM_TEAM_3); - pinkowned += (it.goalentity.team == NUM_TEAM_4); - }); -} - -float Domination_GetWinnerTeam() -{ - float winner_team = 0; - if(redowned == total_controlpoints) - winner_team = NUM_TEAM_1; - if(blueowned == total_controlpoints) - { - if(winner_team) return 0; - winner_team = NUM_TEAM_2; - } - if(yellowowned == total_controlpoints) - { - if(winner_team) return 0; - winner_team = NUM_TEAM_3; - } - if(pinkowned == total_controlpoints) - { - if(winner_team) return 0; - winner_team = NUM_TEAM_4; - } - if(winner_team) - return winner_team; - return -1; // no control points left? -} - -#define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0)) -#define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints) -float Domination_CheckWinner() -{ - if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) - { - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER); - - game_stopped = true; - round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit); - return 1; - } - - Domination_count_controlpoints(); - - float winner_team = Domination_GetWinnerTeam(); - - if(winner_team == -1) - return 0; - - if(winner_team > 0) - { - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN)); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN)); - TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1); - } - else if(winner_team == -1) - { - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED); - } - - game_stopped = true; - round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit); - - return 1; -} - -float Domination_CheckPlayers() -{ - return 1; -} - -void Domination_RoundStart() -{ - FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; }); -} - -//go to best items, or control points you don't own -void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius) -{ - IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius), - { - if(it.cnt > -1) // this is just being fought - navigation_routerating(this, it, ratingscale, 5000); - else if(it.goalentity.cnt == 0) // unclaimed - navigation_routerating(this, it, ratingscale * 0.5, 5000); - else if(it.goalentity.team != this.team) // other team's point - navigation_routerating(this, it, ratingscale * 0.2, 5000); - }); -} - -void havocbot_role_dom(entity this) -{ - if(IS_DEAD(this)) - return; - - if (navigation_goalrating_timeout(this)) - { - navigation_goalrating_start(this); - havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000); - havocbot_goalrating_items(this, 8000, this.origin, 8000); - //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000); - havocbot_goalrating_waypoints(this, 1, this.origin, 3000); - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - } -} - -MUTATOR_HOOKFUNCTION(dom, CheckAllowedTeams) -{ - // fallback? - M_ARGV(0, float) = domination_teams; - string ret_string = "dom_team"; - - entity head = find(NULL, classname, ret_string); - while(head) - { - if(head.netname != "") - { - switch(head.team) - { - case NUM_TEAM_1: c1 = 0; break; - case NUM_TEAM_2: c2 = 0; break; - case NUM_TEAM_3: c3 = 0; break; - case NUM_TEAM_4: c4 = 0; break; - } - } - - head = find(head, classname, ret_string); - } - - M_ARGV(1, string) = string_null; - - return true; -} - -MUTATOR_HOOKFUNCTION(dom, reset_map_players) -{ - total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0; - FOREACH_CLIENT(IS_PLAYER(it), { - PutClientInServer(it); - if(domination_roundbased) - it.player_blocked = 1; - if(IS_REAL_CLIENT(it)) - set_dom_state(it); - }); - return true; -} - -MUTATOR_HOOKFUNCTION(dom, PlayerSpawn) -{ - entity player = M_ARGV(0, entity); - - if(domination_roundbased) - if(!round_handler_IsRoundStarted()) - player.player_blocked = 1; - else - player.player_blocked = 0; -} - -MUTATOR_HOOKFUNCTION(dom, ClientConnect) -{ - entity player = M_ARGV(0, entity); - - set_dom_state(player); -} - -MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole) -{ - entity bot = M_ARGV(0, entity); - - bot.havocbot_role = havocbot_role_dom; - return true; -} - -/*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32) -Control point for Domination gameplay. -*/ -spawnfunc(dom_controlpoint) -{ - if(!g_domination) - { - delete(this); - return; - } - setthink(this, dom_controlpoint_setup); - this.nextthink = time + 0.1; - this.reset = dom_controlpoint_setup; - - if(!this.scale) - this.scale = 0.6; - - this.effects = this.effects | EF_LOWPRECISION; - if (autocvar_g_domination_point_fullbright) - this.effects |= EF_FULLBRIGHT; - - IL_PUSH(g_dompoints, this); -} - -/*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32) -Team declaration for Domination gameplay, this allows you to decide what team -names and control point models are used in your map. - -Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two -can have netname set! The nameless team owns all control points at start. - -Keys: -"netname" - Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc) -"cnt" - Scoreboard color of the team (for example 4 is red and 13 is blue) -"model" - Model to use for control points owned by this team (for example - "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver - keycard) -"skin" - Skin of the model to use (for team skins on a single model) -"noise" - Sound to play when this team captures a point. - (this is a localized sound, like a small alarm or other effect) -"noise1" - Narrator speech to play when this team captures a point. - (this is a global sound, like "Red team has captured a control point") -*/ - -spawnfunc(dom_team) -{ - if(!g_domination || autocvar_g_domination_teams_override >= 2) - { - delete(this); - return; - } - precache_model(this.model); - if (this.noise != "") - precache_sound(this.noise); - if (this.noise1 != "") - precache_sound(this.noise1); - this.classname = "dom_team"; - _setmodel(this, this.model); // precision not needed - this.mdl = this.model; - this.dmg = this.modelindex; - this.model = ""; - this.modelindex = 0; - // this would have to be changed if used in quakeworld - if(this.cnt) - this.team = this.cnt + 1; // WHY are these different anyway? -} - -// scoreboard setup -void ScoreRules_dom(int teams) -{ - if(domination_roundbased) - { - GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, { - field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY); - field(SP_DOM_TAKES, "takes", 0); - }); - } - else - { - float sp_domticks, sp_score; - sp_score = sp_domticks = 0; - if(autocvar_g_domination_disable_frags) - sp_domticks = SFL_SORT_PRIO_PRIMARY; - else - sp_score = SFL_SORT_PRIO_PRIMARY; - GameRules_scoring(teams, sp_score, sp_score, { - field_team(ST_DOM_TICKS, "ticks", sp_domticks); - field(SP_DOM_TICKS, "ticks", sp_domticks); - field(SP_DOM_TAKES, "takes", 0); - }); - } -} - -// code from here on is just to support maps that don't have control point and team entities -void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, Sound capsound, string capnarration, string capmessage) -{ - TC(Sound, capsound); - entity e = new_pure(dom_team); - e.netname = strzone(teamname); - e.cnt = teamcolor; - e.model = pointmodel; - e.skin = pointskin; - e.noise = strzone(Sound_fixpath(capsound)); - e.noise1 = strzone(capnarration); - e.message = strzone(capmessage); - - // this code is identical to spawnfunc_dom_team - _setmodel(e, e.model); // precision not needed - e.mdl = e.model; - e.dmg = e.modelindex; - e.model = ""; - e.modelindex = 0; - // this would have to be changed if used in quakeworld - e.team = e.cnt + 1; - - //eprint(e); -} - -void dom_spawnpoint(vector org) -{ - entity e = spawn(); - e.classname = "dom_controlpoint"; - setthink(e, spawnfunc_dom_controlpoint); - e.nextthink = time; - setorigin(e, org); - spawnfunc_dom_controlpoint(e); -} - -// spawn some default teams if the map is not set up for domination -void dom_spawnteams(int teams) -{ - TC(int, teams); - dom_spawnteam(Team_ColoredFullName(NUM_TEAM_1), NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND_DOM_CLAIM, "", "Red team has captured a control point"); - dom_spawnteam(Team_ColoredFullName(NUM_TEAM_2), NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND_DOM_CLAIM, "", "Blue team has captured a control point"); - if(teams >= 3) - dom_spawnteam(Team_ColoredFullName(NUM_TEAM_3), NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, SND_DOM_CLAIM, "", "Yellow team has captured a control point"); - if(teams >= 4) - dom_spawnteam(Team_ColoredFullName(NUM_TEAM_4), NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, SND_DOM_CLAIM, "", "Pink team has captured a control point"); - dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, SND_Null, "", ""); -} - -void dom_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up. -{ - // if no teams are found, spawn defaults - if(find(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2) - { - LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway."); - domination_teams = autocvar_g_domination_teams_override; - if (domination_teams < 2) - domination_teams = autocvar_g_domination_default_teams; - domination_teams = bound(2, domination_teams, 4); - dom_spawnteams(domination_teams); - } - - CheckAllowedTeams(NULL); - //domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2); - - int teams = 0; - if(c1 >= 0) teams |= BIT(0); - if(c2 >= 0) teams |= BIT(1); - if(c3 >= 0) teams |= BIT(2); - if(c4 >= 0) teams |= BIT(3); - domination_teams = teams; - - domination_roundbased = autocvar_g_domination_roundbased; - - ScoreRules_dom(domination_teams); - - if(domination_roundbased) - { - round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart); - round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit); - } -} - -void dom_Initialize() -{ - g_domination = true; - InitializeEntity(NULL, dom_DelayedInit, INITPRIO_GAMETYPE); -} diff --git a/qcsrc/server/mutators/mutator/gamemode_domination.qh b/qcsrc/server/mutators/mutator/gamemode_domination.qh deleted file mode 100644 index 95311c98a..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_domination.qh +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include "../gamemode.qh" - -#define autocvar_g_domination_point_limit cvar("g_domination_point_limit") -bool autocvar_g_domination_roundbased; -int autocvar_g_domination_roundbased_point_limit; -int autocvar_g_domination_point_leadlimit; - -void dom_Initialize(); - -REGISTER_MUTATOR(dom, false) -{ - MUTATOR_STATIC(); - MUTATOR_ONADD - { - int fraglimit_override = autocvar_g_domination_point_limit; - if (autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit) - fraglimit_override = autocvar_g_domination_roundbased_point_limit; - - GameRules_teams(true); - GameRules_limit_score(fraglimit_override); - GameRules_limit_lead(autocvar_g_domination_point_leadlimit); - - dom_Initialize(); - } - return 0; -} - -// score rule declarations -const float ST_DOM_TICKS = 1; -const float ST_DOM_CAPS = 1; - -// pps: points per second -float total_pps; -float pps_red; -float pps_blue; -float pps_yellow; -float pps_pink; - -// capture declarations -.float enemy_playerid; -.entity sprite; -.float captime; - -// misc globals -float domination_roundbased; -float domination_teams; - -void AnimateDomPoint(entity this); - -IntrusiveList g_dompoints; -STATIC_INIT(g_dompoints) { g_dompoints = IL_NEW(); } diff --git a/qcsrc/server/mutators/mutator/gamemode_freezetag.qc b/qcsrc/server/mutators/mutator/gamemode_freezetag.qc deleted file mode 100644 index 36546c43a..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_freezetag.qc +++ /dev/null @@ -1,585 +0,0 @@ -#include "gamemode_freezetag.qh" - -float autocvar_g_freezetag_frozen_maxtime; -float autocvar_g_freezetag_revive_clearspeed; -float autocvar_g_freezetag_round_timelimit; -//int autocvar_g_freezetag_teams; -int autocvar_g_freezetag_teams_override; -float autocvar_g_freezetag_warmup; - -void freezetag_count_alive_players() -{ - total_players = redalive = bluealive = yellowalive = pinkalive = 0; - FOREACH_CLIENT(IS_PLAYER(it), { - switch(it.team) - { - case NUM_TEAM_1: ++total_players; if(it.health >= 1 && STAT(FROZEN, it) != 1) ++redalive; break; - case NUM_TEAM_2: ++total_players; if(it.health >= 1 && STAT(FROZEN, it) != 1) ++bluealive; break; - case NUM_TEAM_3: ++total_players; if(it.health >= 1 && STAT(FROZEN, it) != 1) ++yellowalive; break; - case NUM_TEAM_4: ++total_players; if(it.health >= 1 && STAT(FROZEN, it) != 1) ++pinkalive; break; - } - }); - FOREACH_CLIENT(IS_REAL_CLIENT(it), { - STAT(REDALIVE, it) = redalive; - STAT(BLUEALIVE, it) = bluealive; - STAT(YELLOWALIVE, it) = yellowalive; - STAT(PINKALIVE, it) = pinkalive; - }); - - eliminatedPlayers.SendFlags |= 1; -} -#define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0)) -#define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == NumTeams(freezetag_teams)) - -float freezetag_CheckTeams() -{ - static float prev_missing_teams_mask; - if(FREEZETAG_ALIVE_TEAMS_OK()) - { - if(prev_missing_teams_mask > 0) - Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS); - prev_missing_teams_mask = -1; - return 1; - } - if(total_players == 0) - { - if(prev_missing_teams_mask > 0) - Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS); - prev_missing_teams_mask = -1; - return 0; - } - int missing_teams_mask = 0; - if(freezetag_teams & BIT(0)) - missing_teams_mask += (!redalive) * 1; - if(freezetag_teams & BIT(1)) - missing_teams_mask += (!bluealive) * 2; - if(freezetag_teams & BIT(2)) - missing_teams_mask += (!yellowalive) * 4; - if(freezetag_teams & BIT(3)) - missing_teams_mask += (!pinkalive) * 8; - if(prev_missing_teams_mask != missing_teams_mask) - { - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask); - prev_missing_teams_mask = missing_teams_mask; - } - return 0; -} - -float freezetag_getWinnerTeam() -{ - float winner_team = 0; - if(redalive >= 1) - winner_team = NUM_TEAM_1; - if(bluealive >= 1) - { - if(winner_team) return 0; - winner_team = NUM_TEAM_2; - } - if(yellowalive >= 1) - { - if(winner_team) return 0; - winner_team = NUM_TEAM_3; - } - if(pinkalive >= 1) - { - if(winner_team) return 0; - winner_team = NUM_TEAM_4; - } - if(winner_team) - return winner_team; - return -1; // no player left -} - -void nades_Clear(entity); -void nades_GiveBonus(entity player, float score); - -float freezetag_CheckWinner() -{ - if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) - { - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER); - FOREACH_CLIENT(IS_PLAYER(it), { - it.freezetag_frozen_timeout = 0; - nades_Clear(it); - }); - game_stopped = true; - round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit); - return 1; - } - - if(FREEZETAG_ALIVE_TEAMS() > 1) - return 0; - - int winner_team = freezetag_getWinnerTeam(); - if(winner_team > 0) - { - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN)); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN)); - TeamScore_AddToTeam(winner_team, ST_SCORE, +1); - } - else if(winner_team == -1) - { - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED); - } - - FOREACH_CLIENT(IS_PLAYER(it), { - it.freezetag_frozen_timeout = 0; - nades_Clear(it); - }); - - game_stopped = true; - round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit); - return 1; -} - -entity freezetag_LastPlayerForTeam(entity this) -{ - entity last_pl = NULL; - FOREACH_CLIENT(IS_PLAYER(it) && it != this, { - if(it.health >= 1) - if(!STAT(FROZEN, it)) - if(SAME_TEAM(it, this)) - if(!last_pl) - last_pl = it; - else - return NULL; - }); - return last_pl; -} - -void freezetag_LastPlayerForTeam_Notify(entity this) -{ - if(round_handler_IsActive()) - if(round_handler_IsRoundStarted()) - { - entity pl = freezetag_LastPlayerForTeam(this); - if(pl) - Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE); - } -} - -void freezetag_Add_Score(entity targ, entity attacker) -{ - if(attacker == targ) - { - // you froze your own dumb targ - // counted as "suicide" already - GameRules_scoring_add(targ, SCORE, -1); - } - else if(IS_PLAYER(attacker)) - { - // got frozen by an enemy - // counted as "kill" and "death" already - GameRules_scoring_add(targ, SCORE, -1); - GameRules_scoring_add(attacker, SCORE, +1); - } - // else nothing - got frozen by the game type rules themselves -} - -void freezetag_Freeze(entity targ, entity attacker) -{ - if(STAT(FROZEN, targ)) - return; - - if(autocvar_g_freezetag_frozen_maxtime > 0) - targ.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime; - - Freeze(targ, 0, 1, true); - - freezetag_count_alive_players(); - - freezetag_Add_Score(targ, attacker); -} - -void freezetag_Unfreeze(entity this) -{ - this.freezetag_frozen_time = 0; - this.freezetag_frozen_timeout = 0; - - Unfreeze(this); -} - -float freezetag_isEliminated(entity e) -{ - if(IS_PLAYER(e) && (STAT(FROZEN, e) == 1 || IS_DEAD(e))) - return true; - return false; -} - - -// ================ -// Bot player logic -// ================ - -void(entity this) havocbot_role_ft_freeing; -void(entity this) havocbot_role_ft_offense; - -void havocbot_goalrating_freeplayers(entity this, float ratingscale, vector org, float sradius) -{ - float t; - FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), { - if (STAT(FROZEN, it) == 1) - { - if(vdist(it.origin - org, >, sradius)) - continue; - navigation_routerating(this, it, ratingscale, 2000); - } - else if(vdist(it.origin - org, >, 400)) // avoid gathering all teammates in one place - { - // If teamate is not frozen still seek them out as fight better - // in a group. - t = 0.2 * 150 / (this.health + this.armorvalue); - navigation_routerating(this, it, t * ratingscale, 2000); - } - }); -} - -void havocbot_role_ft_offense(entity this) -{ - if(IS_DEAD(this)) - return; - - if (!this.havocbot_role_timeout) - this.havocbot_role_timeout = time + random() * 10 + 20; - - // Count how many players on team are unfrozen. - int unfrozen = 0; - FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this) && !(STAT(FROZEN, it) != 1), { unfrozen++; }); - - // If only one left on team or if role has timed out then start trying to free players. - if (((unfrozen == 0) && (!STAT(FROZEN, this))) || (time > this.havocbot_role_timeout)) - { - LOG_TRACE("changing role to freeing"); - this.havocbot_role = havocbot_role_ft_freeing; - this.havocbot_role_timeout = 0; - return; - } - - if (navigation_goalrating_timeout(this)) - { - navigation_goalrating_start(this); - havocbot_goalrating_items(this, 10000, this.origin, 10000); - havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000); - havocbot_goalrating_freeplayers(this, 9000, this.origin, 10000); - havocbot_goalrating_waypoints(this, 1, this.origin, 3000); - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - } -} - -void havocbot_role_ft_freeing(entity this) -{ - if(IS_DEAD(this)) - return; - - if (!this.havocbot_role_timeout) - this.havocbot_role_timeout = time + random() * 10 + 20; - - if (time > this.havocbot_role_timeout) - { - LOG_TRACE("changing role to offense"); - this.havocbot_role = havocbot_role_ft_offense; - this.havocbot_role_timeout = 0; - return; - } - - if (navigation_goalrating_timeout(this)) - { - navigation_goalrating_start(this); - havocbot_goalrating_items(this, 8000, this.origin, 10000); - havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000); - havocbot_goalrating_freeplayers(this, 20000, this.origin, 10000); - havocbot_goalrating_waypoints(this, 1, this.origin, 3000); - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - } -} - - -// ============== -// Hook Functions -// ============== - -void ft_RemovePlayer(entity this) -{ - this.health = 0; // neccessary to update correctly alive stats - if(!STAT(FROZEN, this)) - freezetag_LastPlayerForTeam_Notify(this); - freezetag_Unfreeze(this); - freezetag_count_alive_players(); -} - -MUTATOR_HOOKFUNCTION(ft, ClientDisconnect) -{ - entity player = M_ARGV(0, entity); - - ft_RemovePlayer(player); - return true; -} - -MUTATOR_HOOKFUNCTION(ft, MakePlayerObserver) -{ - entity player = M_ARGV(0, entity); - - ft_RemovePlayer(player); -} - -MUTATOR_HOOKFUNCTION(ft, PlayerDies) -{ - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - float frag_deathtype = M_ARGV(3, float); - - if(round_handler_IsActive()) - if(round_handler_CountdownRunning()) - { - if(STAT(FROZEN, frag_target)) - freezetag_Unfreeze(frag_target); - freezetag_count_alive_players(); - return true; // let the player die so that he can respawn whenever he wants - } - - // Cases DEATH_TEAMCHANGE and DEATH_AUTOTEAMCHANGE are needed to fix a bug whe - // you succeed changing team through the menu: you both really die (gibbing) and get frozen - if(ITEM_DAMAGE_NEEDKILL(frag_deathtype) - || frag_deathtype == DEATH_TEAMCHANGE.m_id || frag_deathtype == DEATH_AUTOTEAMCHANGE.m_id) - { - // let the player die, he will be automatically frozen when he respawns - if(STAT(FROZEN, frag_target) != 1) - { - freezetag_Add_Score(frag_target, frag_attacker); - freezetag_count_alive_players(); - freezetag_LastPlayerForTeam_Notify(frag_target); - } - else - freezetag_Unfreeze(frag_target); // remove ice - frag_target.health = 0; // Unfreeze resets health - frag_target.freezetag_frozen_timeout = -2; // freeze on respawn - return true; - } - - if(STAT(FROZEN, frag_target)) - return true; - - freezetag_Freeze(frag_target, frag_attacker); - freezetag_LastPlayerForTeam_Notify(frag_target); - - if(frag_attacker == frag_target || frag_attacker == NULL) - { - if(IS_PLAYER(frag_target)) - Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname); - } - else - { - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname); - } - - return true; -} - -MUTATOR_HOOKFUNCTION(ft, PlayerSpawn) -{ - entity player = M_ARGV(0, entity); - - if(player.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players - return true; // do nothing, round is starting right now - - if(player.freezetag_frozen_timeout == -2) // player was dead - { - freezetag_Freeze(player, NULL); - return true; - } - - freezetag_count_alive_players(); - - if(round_handler_IsActive()) - if(round_handler_IsRoundStarted()) - { - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_SPAWN_LATE); - freezetag_Freeze(player, NULL); - } - - return true; -} - -MUTATOR_HOOKFUNCTION(ft, reset_map_players) -{ - FOREACH_CLIENT(IS_PLAYER(it), { - CS(it).killcount = 0; - it.freezetag_frozen_timeout = -1; - PutClientInServer(it); - it.freezetag_frozen_timeout = 0; - }); - freezetag_count_alive_players(); - return true; -} - -MUTATOR_HOOKFUNCTION(ft, GiveFragsForKill, CBC_ORDER_FIRST) -{ - M_ARGV(2, float) = 0; // no frags counted in Freeze Tag - return true; -} - -MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST) -{ - if(game_stopped) - return true; - - if(round_handler_IsActive()) - if(!round_handler_IsRoundStarted()) - return true; - - int n; - entity o = NULL; - entity player = M_ARGV(0, entity); - //if(STAT(FROZEN, player)) - //if(player.freezetag_frozen_timeout > 0 && time < player.freezetag_frozen_timeout) - //player.iceblock.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (player.freezetag_frozen_timeout - time) / (player.freezetag_frozen_timeout - player.freezetag_frozen_time); - - if(player.freezetag_frozen_timeout > 0 && time >= player.freezetag_frozen_timeout) - n = -1; - else - { - vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size; - n = 0; - FOREACH_CLIENT(IS_PLAYER(it) && it != player, { - if(STAT(FROZEN, it) == 0) - if(!IS_DEAD(it)) - if(SAME_TEAM(it, player)) - if(boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax)) - { - if(!o) - o = it; - if(STAT(FROZEN, player) == 1) - it.reviving = true; - ++n; - } - }); - - } - - if(n && STAT(FROZEN, player) == 1) // OK, there is at least one teammate reviving us - { - STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1); - player.health = max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health)); - - if(STAT(REVIVE_PROGRESS, player) >= 1) - { - freezetag_Unfreeze(player); - freezetag_count_alive_players(); - - if(n == -1) - { - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, player.netname, autocvar_g_freezetag_frozen_maxtime); - return true; - } - - // EVERY team mate nearby gets a point (even if multiple!) - FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, { - GameRules_scoring_add(it, FREEZETAG_REVIVALS, +1); - GameRules_scoring_add(it, SCORE, +1); - nades_GiveBonus(it,autocvar_g_nades_bonus_score_low); - }); - - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname); - Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED, player.netname, o.netname); - } - - FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, { - STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player); - it.reviving = false; - }); - } - else if(!n && STAT(FROZEN, player) == 1) // only if no teammate is nearby will we reset - { - STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) - frametime * autocvar_g_freezetag_revive_clearspeed, 1); - player.health = max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health)); - } - else if(!n && !STAT(FROZEN, player)) - { - STAT(REVIVE_PROGRESS, player) = 0; // thawing nobody - } - - return true; -} - -MUTATOR_HOOKFUNCTION(ft, SetStartItems) -{ - start_items &= ~IT_UNLIMITED_AMMO; - //start_health = warmup_start_health = cvar("g_lms_start_health"); - //start_armorvalue = warmup_start_armorvalue = cvar("g_lms_start_armor"); - start_ammo_shells = warmup_start_ammo_shells = cvar("g_lms_start_ammo_shells"); - start_ammo_nails = warmup_start_ammo_nails = cvar("g_lms_start_ammo_nails"); - start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets"); - start_ammo_cells = warmup_start_ammo_cells = cvar("g_lms_start_ammo_cells"); - start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_lms_start_ammo_plasma"); - start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_lms_start_ammo_fuel"); -} - -MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole) -{ - entity bot = M_ARGV(0, entity); - - if (!IS_DEAD(bot)) - { - if (random() < 0.5) - bot.havocbot_role = havocbot_role_ft_freeing; - else - bot.havocbot_role = havocbot_role_ft_offense; - } - - return true; -} - -MUTATOR_HOOKFUNCTION(ft, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE) -{ - M_ARGV(0, float) = freezetag_teams; -} - -MUTATOR_HOOKFUNCTION(ft, SetWeaponArena) -{ - // most weapons arena - if(M_ARGV(0, string) == "0" || M_ARGV(0, string) == "") - M_ARGV(0, string) = "most"; -} - -MUTATOR_HOOKFUNCTION(ft, FragCenterMessage) -{ - entity frag_attacker = M_ARGV(0, entity); - entity frag_target = M_ARGV(1, entity); - //float frag_deathtype = M_ARGV(2, float); - int kill_count_to_attacker = M_ARGV(3, int); - int kill_count_to_target = M_ARGV(4, int); - - if(STAT(FROZEN, frag_target)) - return; // target was already frozen, so this is just pushing them off the cliff - - Send_Notification(NOTIF_ONE, frag_attacker, MSG_CHOICE, CHOICE_FRAG_FREEZE, frag_target.netname, kill_count_to_attacker, (IS_BOT_CLIENT(frag_target) ? -1 : CS(frag_target).ping)); - Send_Notification(NOTIF_ONE, frag_target, MSG_CHOICE, CHOICE_FRAGGED_FREEZE, frag_attacker.netname, kill_count_to_target, frag_attacker.health, frag_attacker.armorvalue, (IS_BOT_CLIENT(frag_attacker) ? -1 : CS(frag_attacker).ping)); - - return true; -} - -void freezetag_Initialize() -{ - freezetag_teams = autocvar_g_freezetag_teams_override; - if(freezetag_teams < 2) - freezetag_teams = cvar("g_freezetag_teams"); // read the cvar directly as it gets written earlier in the same frame - - freezetag_teams = BITS(bound(2, freezetag_teams, 4)); - GameRules_scoring(freezetag_teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, { - field(SP_FREEZETAG_REVIVALS, "revivals", 0); - }); - - round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null); - round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit); - - EliminatedPlayers_Init(freezetag_isEliminated); -} diff --git a/qcsrc/server/mutators/mutator/gamemode_freezetag.qh b/qcsrc/server/mutators/mutator/gamemode_freezetag.qh deleted file mode 100644 index a258d82ea..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_freezetag.qh +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include "../gamemode.qh" - -int autocvar_g_freezetag_point_limit; -int autocvar_g_freezetag_point_leadlimit; -bool autocvar_g_freezetag_team_spawns; -void freezetag_Initialize(); - -REGISTER_MUTATOR(ft, false) -{ - MUTATOR_STATIC(); - MUTATOR_ONADD - { - GameRules_teams(true); - GameRules_spawning_teams(autocvar_g_freezetag_team_spawns); - GameRules_limit_score(autocvar_g_freezetag_point_limit); - GameRules_limit_lead(autocvar_g_freezetag_point_leadlimit); - - freezetag_Initialize(); - } - return 0; -} - -.float freezetag_frozen_time; -.float freezetag_frozen_timeout; -const float ICE_MAX_ALPHA = 1; -const float ICE_MIN_ALPHA = 0.1; -float freezetag_teams; - -.float reviving; // temp var - -float autocvar_g_freezetag_revive_extra_size; -float autocvar_g_freezetag_revive_speed; -bool autocvar_g_freezetag_revive_nade; -float autocvar_g_freezetag_revive_nade_health; diff --git a/qcsrc/server/mutators/mutator/gamemode_invasion.qc b/qcsrc/server/mutators/mutator/gamemode_invasion.qc deleted file mode 100644 index 777b1b1e9..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_invasion.qc +++ /dev/null @@ -1,604 +0,0 @@ -#include "gamemode_invasion.qh" - -#include -#include - -#include - -IntrusiveList g_invasion_roundends; -IntrusiveList g_invasion_waves; -IntrusiveList g_invasion_spawns; -STATIC_INIT(g_invasion) -{ - g_invasion_roundends = IL_NEW(); - g_invasion_waves = IL_NEW(); - g_invasion_spawns = IL_NEW(); -} - -float autocvar_g_invasion_round_timelimit; -float autocvar_g_invasion_spawnpoint_spawn_delay; -float autocvar_g_invasion_warmup; -int autocvar_g_invasion_monster_count; -bool autocvar_g_invasion_zombies_only; -float autocvar_g_invasion_spawn_delay; - -bool victent_present; -.bool inv_endreached; - -bool inv_warning_shown; // spammy - -.string spawnmob; - -void target_invasion_roundend_use(entity this, entity actor, entity trigger) -{ - if(!IS_PLAYER(actor)) { return; } - - actor.inv_endreached = true; - - int plnum = 0; - int realplnum = 0; - // let's not count bots - FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { - ++realplnum; - if(it.inv_endreached) - ++plnum; - }); - if(plnum < ceil(realplnum * min(1, this.count))) // 70% of players - return; - - this.winning = true; -} - -spawnfunc(target_invasion_roundend) -{ - if(!g_invasion) { delete(this); return; } - - victent_present = true; // a victory entity is present, we don't need to rely on monster count TODO: merge this with the intrusive list (can check empty) - - if(!this.count) { this.count = 0.7; } // require at least 70% of the players to reach the end before triggering victory - - this.use = target_invasion_roundend_use; - - IL_PUSH(g_invasion_roundends, this); -} - -spawnfunc(invasion_wave) -{ - if(!g_invasion) { delete(this); return; } - - IL_PUSH(g_invasion_waves, this); -} - -spawnfunc(invasion_spawnpoint) -{ - if(!g_invasion) { delete(this); return; } - - this.classname = "invasion_spawnpoint"; - IL_PUSH(g_invasion_spawns, this); -} - -void ClearWinners(); - -// Invasion stage mode winning condition: If the attackers triggered a round end (by fulfilling all objectives) -// they win. -int WinningCondition_Invasion() -{ - WinningConditionHelper(NULL); // set worldstatus - - int status = WINNING_NO; - - if(autocvar_g_invasion_type == INV_TYPE_STAGE) - { - SetWinners(inv_endreached, true); - - int found = 0; - IL_EACH(g_invasion_roundends, true, - { - ++found; - if(it.winning) - { - bprint("Invasion: round completed.\n"); - // winners already set (TODO: teamplay support) - - status = WINNING_YES; - break; - } - }); - - if(!found) - status = WINNING_YES; // just end it? TODO: should warn mapper! - } - else if(autocvar_g_invasion_type == INV_TYPE_HUNT) - { - ClearWinners(); - - int found = 0; // NOTE: this ends the round if no monsters are placed - IL_EACH(g_monsters, !(it.spawnflags & MONSTERFLAG_RESPAWNED), - { - ++found; - }); - - if(found <= 0) - { - FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), - { - it.winning = true; - }); - status = WINNING_YES; - } - } - - return status; -} - -Monster invasion_PickMonster(int supermonster_count) -{ - RandomSelection_Init(); - - FOREACH(Monsters, it != MON_Null, - { - if((it.spawnflags & MON_FLAG_HIDDEN) || (it.spawnflags & MONSTER_TYPE_PASSIVE) || (it.spawnflags & MONSTER_TYPE_FLY) || (it.spawnflags & MONSTER_TYPE_SWIM) || - (it.spawnflags & MONSTER_SIZE_QUAKE) || ((it.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1)) - continue; - if(autocvar_g_invasion_zombies_only && !(it.spawnflags & MONSTER_TYPE_UNDEAD)) - continue; - RandomSelection_AddEnt(it, 1, 1); - }); - - return RandomSelection_chosen_ent; -} - -entity invasion_PickSpawn() -{ - RandomSelection_Init(); - - IL_EACH(g_invasion_spawns, true, - { - RandomSelection_AddEnt(it, 1, ((time < it.spawnshieldtime) ? 0.2 : 1)); // give recently used spawnpoints a very low rating - it.spawnshieldtime = time + autocvar_g_invasion_spawnpoint_spawn_delay; - }); - - return RandomSelection_chosen_ent; -} - -entity invasion_GetWaveEntity(int wavenum) -{ - IL_EACH(g_invasion_waves, it.cnt == wavenum, - { - return it; // found one - }); - - // if no specific one is found, find the last existing wave ent - entity best = NULL; - IL_EACH(g_invasion_waves, it.cnt <= wavenum, - { - if(!best || it.cnt > best.cnt) - best = it; - }); - - return best; -} - -void invasion_SpawnChosenMonster(Monster mon) -{ - entity monster; - entity spawn_point = invasion_PickSpawn(); - entity wave_ent = invasion_GetWaveEntity(inv_roundcnt); - - string tospawn = ""; - if(wave_ent && wave_ent.spawnmob && wave_ent.spawnmob != "") - { - RandomSelection_Init(); - FOREACH_WORD(wave_ent.spawnmob, true, - { - RandomSelection_AddString(it, 1, 1); - }); - - tospawn = RandomSelection_chosen_string; - } - - if(spawn_point == NULL) - { - if(!inv_warning_shown) - { - inv_warning_shown = true; - LOG_TRACE("Warning: couldn't find any invasion_spawnpoint spawnpoints, attempting to spawn monsters in random locations"); - } - entity e = spawn(); - setsize(e, mon.m_mins, mon.m_maxs); - - if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256)) - monster = spawnmonster(e, tospawn, mon.monsterid, NULL, NULL, e.origin, false, false, 2); - else - { - delete(e); - return; - } - } - else // if spawnmob field falls through (unset), fallback to mon (relying on spawnmonster for that behaviour) - monster = spawnmonster(spawn(), ((spawn_point.spawnmob && spawn_point.spawnmob != "") ? spawn_point.spawnmob : tospawn), mon.monsterid, spawn_point, spawn_point, spawn_point.origin, false, false, 2); - - if(!monster) - return; - - monster.spawnshieldtime = time; - - if(spawn_point) - { - if(spawn_point.target_range) - monster.target_range = spawn_point.target_range; - monster.target2 = spawn_point.target2; - } - - if(teamplay) - { - if(spawn_point && spawn_point.team && inv_monsters_perteam[spawn_point.team] > 0) - monster.team = spawn_point.team; - else - { - RandomSelection_Init(); - if(inv_monsters_perteam[NUM_TEAM_1] > 0) RandomSelection_AddFloat(NUM_TEAM_1, 1, 1); - if(inv_monsters_perteam[NUM_TEAM_2] > 0) RandomSelection_AddFloat(NUM_TEAM_2, 1, 1); - if(invasion_teams >= 3) if(inv_monsters_perteam[NUM_TEAM_3] > 0) { RandomSelection_AddFloat(NUM_TEAM_3, 1, 1); } - if(invasion_teams >= 4) if(inv_monsters_perteam[NUM_TEAM_4] > 0) { RandomSelection_AddFloat(NUM_TEAM_4, 1, 1); } - - monster.team = RandomSelection_chosen_float; - } - - monster_setupcolors(monster); - - if(monster.sprite) - { - WaypointSprite_UpdateTeamRadar(monster.sprite, RADARICON_DANGER, ((monster.team) ? Team_ColorRGB(monster.team) : '1 0 0')); - - monster.sprite.team = 0; - monster.sprite.SendFlags |= 1; - } - } - - if(monster.monster_attack) - IL_REMOVE(g_monster_targets, monster); - monster.monster_attack = false; // it's the player's job to kill all the monsters - - if(inv_roundcnt >= inv_maxrounds) - monster.spawnflags |= MONSTERFLAG_MINIBOSS; // last round spawns minibosses -} - -void invasion_SpawnMonsters(int supermonster_count) -{ - Monster chosen_monster = invasion_PickMonster(supermonster_count); - - invasion_SpawnChosenMonster(chosen_monster); -} - -bool Invasion_CheckWinner() -{ - if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) - { - IL_EACH(g_monsters, true, - { - Monster_Remove(it); - }); - IL_CLEAR(g_monsters); - - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER); - round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit); - return 1; - } - - float total_alive_monsters = 0, supermonster_count = 0, red_alive = 0, blue_alive = 0, yellow_alive = 0, pink_alive = 0; - - IL_EACH(g_monsters, it.health > 0, - { - if((get_monsterinfo(it.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER) - ++supermonster_count; - ++total_alive_monsters; - - if(teamplay) - switch(it.team) - { - case NUM_TEAM_1: ++red_alive; break; - case NUM_TEAM_2: ++blue_alive; break; - case NUM_TEAM_3: ++yellow_alive; break; - case NUM_TEAM_4: ++pink_alive; break; - } - }); - - if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < inv_maxspawned) - { - if(time >= inv_lastcheck) - { - invasion_SpawnMonsters(supermonster_count); - inv_lastcheck = time + autocvar_g_invasion_spawn_delay; - } - - return 0; - } - - if(inv_numspawned < 1) - return 0; // nothing has spawned yet - - if(teamplay) - { - if(((red_alive > 0) + (blue_alive > 0) + (yellow_alive > 0) + (pink_alive > 0)) > 1) - return 0; - } - else if(inv_numkilled < inv_maxspawned) - return 0; - - entity winner = NULL; - float winning_score = 0, winner_team = 0; - - - if(teamplay) - { - if(red_alive > 0) { winner_team = NUM_TEAM_1; } - if(blue_alive > 0) - if(winner_team) { winner_team = 0; } - else { winner_team = NUM_TEAM_2; } - if(yellow_alive > 0) - if(winner_team) { winner_team = 0; } - else { winner_team = NUM_TEAM_3; } - if(pink_alive > 0) - if(winner_team) { winner_team = 0; } - else { winner_team = NUM_TEAM_4; } - } - else - { - FOREACH_CLIENT(IS_PLAYER(it), { - float cs = GameRules_scoring_add(it, KILLS, 0); - if(cs > winning_score) - { - winning_score = cs; - winner = it; - } - }); - } - - IL_EACH(g_monsters, true, - { - Monster_Remove(it); - }); - IL_CLEAR(g_monsters); - - if(teamplay) - { - if(winner_team) - { - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN)); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN)); - } - } - else if(winner) - { - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname); - } - - round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit); - - return 1; -} - -bool Invasion_CheckPlayers() -{ - return true; -} - -void Invasion_RoundStart() -{ - int numplayers = 0; - FOREACH_CLIENT(IS_PLAYER(it), { - it.player_blocked = false; - ++numplayers; - }); - - if(inv_roundcnt < inv_maxrounds) - inv_roundcnt += 1; // a limiter to stop crazy counts - - inv_monsterskill = inv_roundcnt + max(1, numplayers * 0.3); - - inv_maxcurrent = 0; - inv_numspawned = 0; - inv_numkilled = 0; - - inv_maxspawned = rint(max(autocvar_g_invasion_monster_count, autocvar_g_invasion_monster_count * (inv_roundcnt * 0.5))); - - if(teamplay) - { - DistributeEvenly_Init(inv_maxspawned, invasion_teams); - inv_monsters_perteam[NUM_TEAM_1] = DistributeEvenly_Get(1); - inv_monsters_perteam[NUM_TEAM_2] = DistributeEvenly_Get(1); - if(invasion_teams >= 3) inv_monsters_perteam[NUM_TEAM_3] = DistributeEvenly_Get(1); - if(invasion_teams >= 4) inv_monsters_perteam[NUM_TEAM_4] = DistributeEvenly_Get(1); - } -} - -MUTATOR_HOOKFUNCTION(inv, MonsterDies) -{ - entity frag_target = M_ARGV(0, entity); - entity frag_attacker = M_ARGV(1, entity); - - if(!(frag_target.spawnflags & MONSTERFLAG_RESPAWNED)) - { - if(autocvar_g_invasion_type == INV_TYPE_ROUND) - { - inv_numkilled += 1; - inv_maxcurrent -= 1; - } - if(teamplay) { inv_monsters_perteam[frag_target.team] -= 1; } - - if(IS_PLAYER(frag_attacker)) - if(SAME_TEAM(frag_attacker, frag_target)) // in non-teamplay modes, same team = same player, so this works - GameRules_scoring_add(frag_attacker, KILLS, -1); - else - { - GameRules_scoring_add(frag_attacker, KILLS, +1); - if(teamplay) - TeamScore_AddToTeam(frag_attacker.team, ST_INV_KILLS, +1); - } - } -} - -MUTATOR_HOOKFUNCTION(inv, MonsterSpawn) -{ - entity mon = M_ARGV(0, entity); - mon.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP; - - if(autocvar_g_invasion_type == INV_TYPE_HUNT) - return false; // allowed - - if(!(mon.spawnflags & MONSTERFLAG_SPAWNED)) - return true; - - if(!(mon.spawnflags & MONSTERFLAG_RESPAWNED)) - { - inv_numspawned += 1; - inv_maxcurrent += 1; - } - - mon.monster_skill = inv_monsterskill; - - if((get_monsterinfo(mon.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER) - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, mon.monster_name); -} - -MUTATOR_HOOKFUNCTION(inv, SV_StartFrame) -{ - if(autocvar_g_invasion_type != INV_TYPE_ROUND) - return; // uses map spawned monsters - - monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned - monsters_killed = inv_numkilled; -} - -MUTATOR_HOOKFUNCTION(inv, PlayerRegen) -{ - // no regeneration in invasion, regardless of the game type - return true; -} - -MUTATOR_HOOKFUNCTION(inv, PlayerSpawn) -{ - entity player = M_ARGV(0, entity); - - if(player.bot_attack) - IL_REMOVE(g_bot_targets, player); - player.bot_attack = false; -} - -MUTATOR_HOOKFUNCTION(inv, Damage_Calculate) -{ - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - float frag_damage = M_ARGV(4, float); - vector frag_force = M_ARGV(6, vector); - - if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker != frag_target) - { - frag_damage = 0; - frag_force = '0 0 0'; - - M_ARGV(4, float) = frag_damage; - M_ARGV(6, vector) = frag_force; - } -} - -MUTATOR_HOOKFUNCTION(inv, BotShouldAttack) -{ - entity targ = M_ARGV(1, entity); - - if(!IS_MONSTER(targ)) - return true; -} - -MUTATOR_HOOKFUNCTION(inv, SetStartItems) -{ - if(autocvar_g_invasion_type == INV_TYPE_ROUND) - { - start_health = 200; - start_armorvalue = 200; - } -} - -MUTATOR_HOOKFUNCTION(inv, AccuracyTargetValid) -{ - entity frag_target = M_ARGV(1, entity); - - if(IS_MONSTER(frag_target)) - return MUT_ACCADD_INVALID; - return MUT_ACCADD_INDIFFERENT; -} - -MUTATOR_HOOKFUNCTION(inv, AllowMobSpawning) -{ - // monster spawning disabled during an invasion - M_ARGV(1, string) = "You cannot spawn monsters during an invasion!"; - return true; -} - -MUTATOR_HOOKFUNCTION(inv, CheckRules_World) -{ - if(autocvar_g_invasion_type == INV_TYPE_ROUND) - return false; - - M_ARGV(0, float) = WinningCondition_Invasion(); - return true; -} - -MUTATOR_HOOKFUNCTION(inv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE) -{ - M_ARGV(0, float) = invasion_teams; -} - -MUTATOR_HOOKFUNCTION(inv, AllowMobButcher) -{ - M_ARGV(0, string) = "This command does not work during an invasion!"; - return true; -} - -void invasion_ScoreRules(int inv_teams) -{ - if(inv_teams) { CheckAllowedTeams(NULL); } - GameRules_score_enabled(false); - GameRules_scoring(inv_teams, 0, 0, { - if (inv_teams) { - field_team(ST_INV_KILLS, "frags", SFL_SORT_PRIO_PRIMARY); - } - field(SP_KILLS, "frags", ((inv_teams) ? SFL_SORT_PRIO_SECONDARY : SFL_SORT_PRIO_PRIMARY)); - }); -} - -void invasion_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up. -{ - if(autocvar_g_invasion_type == INV_TYPE_HUNT || autocvar_g_invasion_type == INV_TYPE_STAGE) - cvar_set("fraglimit", "0"); - - if(autocvar_g_invasion_teams) - { - invasion_teams = BITS(bound(2, autocvar_g_invasion_teams, 4)); - } - else - invasion_teams = 0; - - independent_players = 1; // to disable extra useless scores - - invasion_ScoreRules(invasion_teams); - - independent_players = 0; - - if(autocvar_g_invasion_type == INV_TYPE_ROUND) - { - round_handler_Spawn(Invasion_CheckPlayers, Invasion_CheckWinner, Invasion_RoundStart); - round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit); - - inv_roundcnt = 0; - inv_maxrounds = 15; // 15? - } -} - -void invasion_Initialize() -{ - InitializeEntity(NULL, invasion_DelayedInit, INITPRIO_GAMETYPE); -} diff --git a/qcsrc/server/mutators/mutator/gamemode_invasion.qh b/qcsrc/server/mutators/mutator/gamemode_invasion.qh deleted file mode 100644 index 0ea0e82c4..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_invasion.qh +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include "../gamemode.qh" - -#define autocvar_g_invasion_point_limit cvar("g_invasion_point_limit") -int autocvar_g_invasion_teams; -int autocvar_g_invasion_type; -bool autocvar_g_invasion_team_spawns; -bool g_invasion; -void invasion_Initialize(); - -REGISTER_MUTATOR(inv, false) -{ - MUTATOR_STATIC(); - MUTATOR_ONADD - { - if (autocvar_g_invasion_teams >= 2) { - GameRules_teams(true); - GameRules_spawning_teams(autocvar_g_invasion_team_spawns); - } - GameRules_limit_score(autocvar_g_invasion_point_limit); - - g_invasion = true; - cvar_settemp("g_monsters", "1"); - invasion_Initialize(); - } - return 0; -} - -float inv_numspawned; -float inv_maxspawned; -float inv_roundcnt; -float inv_maxrounds; -float inv_numkilled; -float inv_lastcheck; -float inv_maxcurrent; - -float invasion_teams; -float inv_monsters_perteam[17]; - -float inv_monsterskill; - -const float ST_INV_KILLS = 1; - -const int INV_TYPE_ROUND = 0; // round-based waves of enemies -const int INV_TYPE_HUNT = 1; // clear the map of placed enemies -const int INV_TYPE_STAGE = 2; // reach the end of the level diff --git a/qcsrc/server/mutators/mutator/gamemode_keepaway.qc b/qcsrc/server/mutators/mutator/gamemode_keepaway.qc deleted file mode 100644 index 567f24b47..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_keepaway.qc +++ /dev/null @@ -1,472 +0,0 @@ -#include "gamemode_keepaway.qh" - -#include - -.entity ballcarried; - -int autocvar_g_keepaway_ballcarrier_effects; -float autocvar_g_keepaway_ballcarrier_damage; -float autocvar_g_keepaway_ballcarrier_force; -float autocvar_g_keepaway_ballcarrier_highspeed; -float autocvar_g_keepaway_ballcarrier_selfdamage; -float autocvar_g_keepaway_ballcarrier_selfforce; -float autocvar_g_keepaway_noncarrier_damage; -float autocvar_g_keepaway_noncarrier_force; -float autocvar_g_keepaway_noncarrier_selfdamage; -float autocvar_g_keepaway_noncarrier_selfforce; -bool autocvar_g_keepaway_noncarrier_warn; -int autocvar_g_keepaway_score_bckill; -int autocvar_g_keepaway_score_killac; -int autocvar_g_keepaway_score_timepoints; -float autocvar_g_keepaway_score_timeinterval; -float autocvar_g_keepawayball_damageforcescale; -int autocvar_g_keepawayball_effects; -float autocvar_g_keepawayball_respawntime; -int autocvar_g_keepawayball_trail_color; - -bool ka_ballcarrier_waypointsprite_visible_for_player(entity this, entity player, entity view) // runs on waypoints which are attached to ballcarriers, updates once per frame -{ - if(view.ballcarried) - if(IS_SPEC(player)) - return false; // we don't want spectators of the ballcarrier to see the attached waypoint on the top of their screen - - // TODO: Make the ballcarrier lack a waypointsprite whenever they have the invisibility powerup - - return true; -} - -void ka_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later -{ - if(autocvar_sv_eventlog) - GameLogEcho(strcat(":ka:", mode, ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : ""))); -} - -void ka_TouchEvent(entity this, entity toucher); -void ka_RespawnBall(entity this) // runs whenever the ball needs to be relocated -{ - if(game_stopped) return; - vector oldballorigin = this.origin; - - if(!MoveToRandomMapLocation(this, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256)) - { - entity spot = SelectSpawnPoint(this, true); - setorigin(this, spot.origin); - this.angles = spot.angles; - } - - makevectors(this.angles); - set_movetype(this, MOVETYPE_BOUNCE); - this.velocity = '0 0 200'; - this.angles = '0 0 0'; - this.effects = autocvar_g_keepawayball_effects; - settouch(this, ka_TouchEvent); - setthink(this, ka_RespawnBall); - this.nextthink = time + autocvar_g_keepawayball_respawntime; - navigation_dynamicgoal_set(this); - - Send_Effect(EFFECT_ELECTRO_COMBO, oldballorigin, '0 0 0', 1); - Send_Effect(EFFECT_ELECTRO_COMBO, this.origin, '0 0 0', 1); - - WaypointSprite_Spawn(WP_KaBall, 0, 0, this, '0 0 64', NULL, this.team, this, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER); - WaypointSprite_Ping(this.waypointsprite_attachedforcarrier); - - sound(this, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere) -} - -void ka_TimeScoring(entity this) -{ - if(this.owner.ballcarried) - { // add points for holding the ball after a certain amount of time - if(autocvar_g_keepaway_score_timepoints) - GameRules_scoring_add(this.owner, SCORE, autocvar_g_keepaway_score_timepoints); - - GameRules_scoring_add(this.owner, KEEPAWAY_BCTIME, (autocvar_g_keepaway_score_timeinterval / 1)); // interval is divided by 1 so that time always shows "seconds" - this.nextthink = time + autocvar_g_keepaway_score_timeinterval; - } -} - -void ka_TouchEvent(entity this, entity toucher) // runs any time that the ball comes in contact with something -{ - if(game_stopped) return; - if(!this) return; - if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) - { // The ball fell off the map, respawn it since players can't get to it - ka_RespawnBall(this); - return; - } - if(IS_DEAD(toucher)) { return; } - if(STAT(FROZEN, toucher)) { return; } - if (!IS_PLAYER(toucher)) - { // The ball just touched an object, most likely the world - Send_Effect(EFFECT_BALL_SPARKS, this.origin, '0 0 0', 1); - sound(this, CH_TRIGGER, SND_KA_TOUCH, VOL_BASE, ATTEN_NORM); - return; - } - else if(this.wait > time) { return; } - - // attach the ball to the player - this.owner = toucher; - toucher.ballcarried = this; - GameRules_scoring_vip(toucher, true); - setattachment(this, toucher, ""); - setorigin(this, '0 0 0'); - - // make the ball invisible/unable to do anything/set up time scoring - this.velocity = '0 0 0'; - set_movetype(this, MOVETYPE_NONE); - this.effects |= EF_NODRAW; - settouch(this, func_null); - setthink(this, ka_TimeScoring); - this.nextthink = time + autocvar_g_keepaway_score_timeinterval; - this.takedamage = DAMAGE_NO; - navigation_dynamicgoal_unset(this); - - // apply effects to player - toucher.glow_color = autocvar_g_keepawayball_trail_color; - toucher.glow_trail = true; - toucher.effects |= autocvar_g_keepaway_ballcarrier_effects; - - // messages and sounds - ka_EventLog("pickup", toucher); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_KEEPAWAY_PICKUP, toucher.netname); - Send_Notification(NOTIF_ALL_EXCEPT, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP, toucher.netname); - Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP_SELF); - sound(this.owner, CH_TRIGGER, SND_KA_PICKEDUP, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere) - - // scoring - GameRules_scoring_add(toucher, KEEPAWAY_PICKUPS, 1); - - // waypoints - WaypointSprite_AttachCarrier(WP_KaBallCarrier, toucher, RADARICON_FLAGCARRIER); - toucher.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = ka_ballcarrier_waypointsprite_visible_for_player; - WaypointSprite_UpdateRule(toucher.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT); - WaypointSprite_Ping(toucher.waypointsprite_attachedforcarrier); - WaypointSprite_Kill(this.waypointsprite_attachedforcarrier); -} - -void ka_DropEvent(entity plyr) // runs any time that a player is supposed to lose the ball -{ - entity ball; - ball = plyr.ballcarried; - - if(!ball) { return; } - - // reset the ball - setattachment(ball, NULL, ""); - set_movetype(ball, MOVETYPE_BOUNCE); - ball.wait = time + 1; - settouch(ball, ka_TouchEvent); - setthink(ball, ka_RespawnBall); - ball.nextthink = time + autocvar_g_keepawayball_respawntime; - ball.takedamage = DAMAGE_YES; - ball.effects &= ~EF_NODRAW; - setorigin(ball, plyr.origin + '0 0 10'); - ball.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom(); - entity e = ball.owner; ball.owner = NULL; - e.ballcarried = NULL; - GameRules_scoring_vip(e, false); - navigation_dynamicgoal_set(ball); - - // reset the player effects - plyr.glow_trail = false; - plyr.effects &= ~autocvar_g_keepaway_ballcarrier_effects; - - // messages and sounds - ka_EventLog("dropped", plyr); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_KEEPAWAY_DROPPED, plyr.netname); - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEEPAWAY_DROPPED, plyr.netname); - sound(NULL, CH_TRIGGER, SND_KA_DROPPED, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere) - - // scoring - // GameRules_scoring_add(plyr, KEEPAWAY_DROPS, 1); Not anymore, this is 100% the same as pickups and is useless. - - // waypoints - WaypointSprite_Spawn(WP_KaBall, 0, 0, ball, '0 0 64', NULL, ball.team, ball, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER); - WaypointSprite_UpdateRule(ball.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT); - WaypointSprite_Ping(ball.waypointsprite_attachedforcarrier); - WaypointSprite_Kill(plyr.waypointsprite_attachedforcarrier); -} - -/** used to clear the ballcarrier whenever the match switches from warmup to normal */ -void ka_Reset(entity this) -{ - if((this.owner) && (IS_PLAYER(this.owner))) - ka_DropEvent(this.owner); - - if(time < game_starttime) - { - setthink(this, ka_RespawnBall); - settouch(this, func_null); - this.nextthink = game_starttime; - } - else - ka_RespawnBall(this); -} - - -// ================ -// Bot player logic -// ================ - -void havocbot_goalrating_ball(entity this, float ratingscale, vector org) -{ - float t; - entity ball_owner; - ball_owner = ka_ball.owner; - - if (ball_owner == this) - return; - - // If ball is carried by player then hunt them down. - if (ball_owner) - { - t = (this.health + this.armorvalue) / (ball_owner.health + ball_owner.armorvalue); - navigation_routerating(this, ball_owner, t * ratingscale, 2000); - } - else // Ball has been dropped so collect. - navigation_routerating(this, ka_ball, ratingscale, 2000); -} - -void havocbot_role_ka_carrier(entity this) -{ - if (IS_DEAD(this)) - return; - - if (navigation_goalrating_timeout(this)) - { - navigation_goalrating_start(this); - havocbot_goalrating_items(this, 10000, this.origin, 10000); - havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000); - havocbot_goalrating_waypoints(this, 1, this.origin, 3000); - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - } - - if (!this.ballcarried) - { - this.havocbot_role = havocbot_role_ka_collector; - navigation_goalrating_timeout_expire(this, 2); - } -} - -void havocbot_role_ka_collector(entity this) -{ - if (IS_DEAD(this)) - return; - - if (navigation_goalrating_timeout(this)) - { - navigation_goalrating_start(this); - havocbot_goalrating_items(this, 10000, this.origin, 10000); - havocbot_goalrating_enemyplayers(this, 1000, this.origin, 10000); - havocbot_goalrating_ball(this, 20000, this.origin); - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - } - - if (this.ballcarried) - { - this.havocbot_role = havocbot_role_ka_carrier; - navigation_goalrating_timeout_expire(this, 2); - } -} - - -// ============== -// Hook Functions -// ============== - -MUTATOR_HOOKFUNCTION(ka, PlayerDies) -{ - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - - if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker))) - { - if(frag_target.ballcarried) { // add to amount of times killing carrier - GameRules_scoring_add(frag_attacker, KEEPAWAY_CARRIERKILLS, 1); - if(autocvar_g_keepaway_score_bckill) // add bckills to the score - GameRules_scoring_add(frag_attacker, SCORE, autocvar_g_keepaway_score_bckill); - } - else if(!frag_attacker.ballcarried) - if(autocvar_g_keepaway_noncarrier_warn) - Send_Notification(NOTIF_ONE_ONLY, frag_attacker, MSG_CENTER, CENTER_KEEPAWAY_WARN); - - if(frag_attacker.ballcarried) // add to amount of kills while ballcarrier - GameRules_scoring_add(frag_attacker, SCORE, autocvar_g_keepaway_score_killac); - } - - if(frag_target.ballcarried) { ka_DropEvent(frag_target); } // a player with the ball has died, drop it -} - -MUTATOR_HOOKFUNCTION(ka, GiveFragsForKill) -{ - M_ARGV(2, float) = 0; // no frags counted in keepaway - return true; // you deceptive little bugger ;3 This needs to be true in order for this function to even count. -} - -MUTATOR_HOOKFUNCTION(ka, PlayerPreThink) -{ - entity player = M_ARGV(0, entity); - - // clear the item used for the ball in keepaway - player.items &= ~IT_KEY1; - - // if the player has the ball, make sure they have the item for it (Used for HUD primarily) - if(player.ballcarried) - player.items |= IT_KEY1; -} - -MUTATOR_HOOKFUNCTION(ka, PlayerUseKey) -{ - entity player = M_ARGV(0, entity); - - if(MUTATOR_RETURNVALUE == 0) - if(player.ballcarried) - { - ka_DropEvent(player); - return true; - } -} - -MUTATOR_HOOKFUNCTION(ka, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc -{ - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - float frag_damage = M_ARGV(4, float); - vector frag_force = M_ARGV(6, vector); - - if(frag_attacker.ballcarried) // if the attacker is a ballcarrier - { - if(frag_target == frag_attacker) // damage done to yourself - { - frag_damage *= autocvar_g_keepaway_ballcarrier_selfdamage; - frag_force *= autocvar_g_keepaway_ballcarrier_selfforce; - } - else // damage done to noncarriers - { - frag_damage *= autocvar_g_keepaway_ballcarrier_damage; - frag_force *= autocvar_g_keepaway_ballcarrier_force; - } - } - else if (!frag_target.ballcarried) // if the target is a noncarrier - { - if(frag_target == frag_attacker) // damage done to yourself - { - frag_damage *= autocvar_g_keepaway_noncarrier_selfdamage; - frag_force *= autocvar_g_keepaway_noncarrier_selfforce; - } - else // damage done to other noncarriers - { - frag_damage *= autocvar_g_keepaway_noncarrier_damage; - frag_force *= autocvar_g_keepaway_noncarrier_force; - } - } - - M_ARGV(4, float) = frag_damage; - M_ARGV(6, vector) = frag_force; -} - -MUTATOR_HOOKFUNCTION(ka, ClientDisconnect) -{ - entity player = M_ARGV(0, entity); - - if(player.ballcarried) { ka_DropEvent(player); } // a player with the ball has left the match, drop it -} - -MUTATOR_HOOKFUNCTION(ka, MakePlayerObserver) -{ - entity player = M_ARGV(0, entity); - - if(player.ballcarried) { ka_DropEvent(player); } // a player with the ball has left the match, drop it -} - -MUTATOR_HOOKFUNCTION(ka, PlayerPowerups) -{ - entity player = M_ARGV(0, entity); - - // In the future this hook is supposed to allow me to do some extra stuff with waypointsprites and invisibility powerup - // So bare with me until I can fix a certain bug with ka_ballcarrier_waypointsprite_visible_for_player() - - player.effects &= ~autocvar_g_keepaway_ballcarrier_effects; - - if(player.ballcarried) - player.effects |= autocvar_g_keepaway_ballcarrier_effects; -} - - -MUTATOR_HOOKFUNCTION(ka, PlayerPhysics_UpdateStats) -{ - entity player = M_ARGV(0, entity); - // these automatically reset, no need to worry - - if(player.ballcarried) - STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_keepaway_ballcarrier_highspeed; -} - -MUTATOR_HOOKFUNCTION(ka, BotShouldAttack) -{ - entity bot = M_ARGV(0, entity); - entity targ = M_ARGV(1, entity); - - // if neither player has ball then don't attack unless the ball is on the ground - if(!targ.ballcarried && !bot.ballcarried && ka_ball.owner) - return true; -} - -MUTATOR_HOOKFUNCTION(ka, HavocBot_ChooseRole) -{ - entity bot = M_ARGV(0, entity); - - if (bot.ballcarried) - bot.havocbot_role = havocbot_role_ka_carrier; - else - bot.havocbot_role = havocbot_role_ka_collector; - return true; -} - -MUTATOR_HOOKFUNCTION(ka, DropSpecialItems) -{ - entity frag_target = M_ARGV(0, entity); - - if(frag_target.ballcarried) - ka_DropEvent(frag_target); -} - -.bool pushable; - -// ============== -// Initialization -// ============== - -MODEL(KA_BALL, "models/orbs/orbblue.md3"); - -void ka_SpawnBall() // loads various values for the ball, runs only once at start of match -{ - entity e = new(keepawayball); - setmodel(e, MDL_KA_BALL); - setsize(e, '-16 -16 -20', '16 16 20'); // 20 20 20 was too big, player is only 16 16 24... gotta cheat with the Z (20) axis so that the particle isn't cut off - e.damageforcescale = autocvar_g_keepawayball_damageforcescale; - e.takedamage = DAMAGE_YES; - e.solid = SOLID_TRIGGER; - set_movetype(e, MOVETYPE_BOUNCE); - e.glow_color = autocvar_g_keepawayball_trail_color; - e.glow_trail = true; - e.flags = FL_ITEM; - IL_PUSH(g_items, e); - e.pushable = true; - e.reset = ka_Reset; - settouch(e, ka_TouchEvent); - e.owner = NULL; - ka_ball = e; - navigation_dynamicgoal_init(ka_ball, false); - - InitializeEntity(e, ka_RespawnBall, INITPRIO_SETLOCATION); // is this the right priority? Neh, I have no idea.. Well-- it works! So. -} - -void ka_Initialize() // run at the start of a match, initiates game mode -{ - ka_SpawnBall(); -} diff --git a/qcsrc/server/mutators/mutator/gamemode_keepaway.qh b/qcsrc/server/mutators/mutator/gamemode_keepaway.qh deleted file mode 100644 index abbabbd5b..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_keepaway.qh +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include "../gamemode.qh" - -void ka_Initialize(); - -REGISTER_MUTATOR(ka, false) -{ - MUTATOR_STATIC(); - MUTATOR_ONADD - { - GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, { - field(SP_KEEPAWAY_PICKUPS, "pickups", 0); - field(SP_KEEPAWAY_CARRIERKILLS, "bckills", 0); - field(SP_KEEPAWAY_BCTIME, "bctime", SFL_SORT_PRIO_SECONDARY); - }); - - ka_Initialize(); - } - return false; -} - - -entity ka_ball; - -void(entity this) havocbot_role_ka_carrier; -void(entity this) havocbot_role_ka_collector; - -void ka_DropEvent(entity plyr); diff --git a/qcsrc/server/mutators/mutator/gamemode_keyhunt.qc b/qcsrc/server/mutators/mutator/gamemode_keyhunt.qc deleted file mode 100644 index 04576486b..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_keyhunt.qc +++ /dev/null @@ -1,1321 +0,0 @@ -#include "gamemode_keyhunt.qh" - -float autocvar_g_balance_keyhunt_damageforcescale; -float autocvar_g_balance_keyhunt_delay_collect; -float autocvar_g_balance_keyhunt_delay_damage_return; -float autocvar_g_balance_keyhunt_delay_return; -float autocvar_g_balance_keyhunt_delay_round; -float autocvar_g_balance_keyhunt_delay_tracking; -float autocvar_g_balance_keyhunt_return_when_unreachable; -float autocvar_g_balance_keyhunt_dropvelocity; -float autocvar_g_balance_keyhunt_maxdist; -float autocvar_g_balance_keyhunt_protecttime; - -int autocvar_g_balance_keyhunt_score_capture; -int autocvar_g_balance_keyhunt_score_carrierfrag; -int autocvar_g_balance_keyhunt_score_collect; -int autocvar_g_balance_keyhunt_score_destroyed; -int autocvar_g_balance_keyhunt_score_destroyed_ownfactor; -int autocvar_g_balance_keyhunt_score_push; -float autocvar_g_balance_keyhunt_throwvelocity; - -//int autocvar_g_keyhunt_teams; -int autocvar_g_keyhunt_teams_override; - -// #define KH_PLAYER_USE_ATTACHMENT -// #define KH_PLAYER_USE_CARRIEDMODEL - -#ifdef KH_PLAYER_USE_ATTACHMENT -const vector KH_PLAYER_ATTACHMENT_DIST_ROTATED = '0 -4 0'; -const vector KH_PLAYER_ATTACHMENT_DIST = '4 0 0'; -const vector KH_PLAYER_ATTACHMENT = '0 0 0'; -const vector KH_PLAYER_ATTACHMENT_ANGLES = '0 0 0'; -const string KH_PLAYER_ATTACHMENT_BONE = ""; -#else -const float KH_KEY_ZSHIFT = 22; -const float KH_KEY_XYDIST = 24; -const float KH_KEY_XYSPEED = 45; -#endif -const float KH_KEY_WP_ZSHIFT = 20; - -const vector KH_KEY_MIN = '-10 -10 -46'; -const vector KH_KEY_MAX = '10 10 3'; -const float KH_KEY_BRIGHTNESS = 2; - -bool kh_no_radar_circles; - -// kh_state -// bits 0- 4: team of key 1, or 0 for no such key, or 30 for dropped, or 31 for self -// bits 5- 9: team of key 2, or 0 for no such key, or 30 for dropped, or 31 for self -// bits 10-14: team of key 3, or 0 for no such key, or 30 for dropped, or 31 for self -// bits 15-19: team of key 4, or 0 for no such key, or 30 for dropped, or 31 for self -.float siren_time; // time delay the siren -//.float stuff_time; // time delay to stuffcmd a cvar - -int kh_keystatus[17]; -//kh_keystatus[0] = status of dropped keys, kh_keystatus[1 - 16] = player # -//replace 17 with cvar("maxplayers") or similar !!!!!!!!! -//for(i = 0; i < maxplayers; ++i) -// kh_keystatus[i] = "0"; - -int kh_Team_ByID(int t) -{ - if(t == 0) return NUM_TEAM_1; - if(t == 1) return NUM_TEAM_2; - if(t == 2) return NUM_TEAM_3; - if(t == 3) return NUM_TEAM_4; - return 0; -} - -//entity kh_worldkeylist; -.entity kh_worldkeynext; -entity kh_controller; -//bool kh_tracking_enabled; -int kh_teams; -int kh_interferemsg_team; -float kh_interferemsg_time; -.entity kh_next, kh_prev; // linked list -.float kh_droptime; -.int kh_dropperteam; -.entity kh_previous_owner; -.int kh_previous_owner_playerid; - -int kh_key_dropped, kh_key_carried; - -int kh_Key_AllOwnedByWhichTeam(); - -const int ST_KH_CAPS = 1; -void kh_ScoreRules(int teams) -{ - GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, { - field_team(ST_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); - field(SP_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); - field(SP_KH_PUSHES, "pushes", 0); - field(SP_KH_DESTROYS, "destroyed", SFL_LOWER_IS_BETTER); - field(SP_KH_PICKUPS, "pickups", 0); - field(SP_KH_KCKILLS, "kckills", 0); - field(SP_KH_LOSSES, "losses", SFL_LOWER_IS_BETTER); - }); -} - -bool kh_KeyCarrier_waypointsprite_visible_for_player(entity this, entity player, entity view) // runs all the time -{ - if(!IS_PLAYER(view) || DIFF_TEAM(this, view)) - if(!kh_tracking_enabled) - return false; - - return true; -} - -bool kh_Key_waypointsprite_visible_for_player(entity this, entity player, entity view) -{ - if(!kh_tracking_enabled) - return false; - if(!this.owner) - return true; - if(!this.owner.owner) - return true; - return false; // draw only when key is not owned -} - -void kh_update_state() -{ - entity key; - int f; - int s = 0; - FOR_EACH_KH_KEY(key) - { - if(key.owner) - f = key.team; - else - f = 30; - s |= (32 ** key.count) * f; - } - - FOREACH_CLIENT(true, { STAT(KH_KEYS, it) = s; }); - - FOR_EACH_KH_KEY(key) - { - if(key.owner) - STAT(KH_KEYS, key.owner) |= (32 ** key.count) * 31; - } - //print(ftos((nextent(NULL)).kh_state), "\n"); -} - - - - -var kh_Think_t kh_Controller_Thinkfunc; -void kh_Controller_SetThink(float t, kh_Think_t func) // runs occasionaly -{ - kh_Controller_Thinkfunc = func; - kh_controller.cnt = ceil(t); - if(t == 0) - kh_controller.nextthink = time; // force -} -void kh_WaitForPlayers(); -void kh_Controller_Think(entity this) // called a lot -{ - if(game_stopped) - return; - if(this.cnt > 0) - { - if(getthink(this) != kh_WaitForPlayers) - this.cnt -= 1; - } - else if(this.cnt == 0) - { - this.cnt -= 1; - kh_Controller_Thinkfunc(); - } - this.nextthink = time + 1; -} - -// frags f: take from cvar * f -// frags 0: no frags -void kh_Scores_Event(entity player, entity key, string what, float frags_player, float frags_owner) // update the score when a key is captured -{ - string s; - if(game_stopped) - return; - - if(frags_player) - UpdateFrags(player, frags_player); - - if(key && key.owner && frags_owner) - UpdateFrags(key.owner, frags_owner); - - if(!autocvar_sv_eventlog) //output extra info to the console or text file - return; - - s = strcat(":keyhunt:", what, ":", ftos(player.playerid), ":", ftos(frags_player)); - - if(key && key.owner) - s = strcat(s, ":", ftos(key.owner.playerid)); - else - s = strcat(s, ":0"); - - s = strcat(s, ":", ftos(frags_owner), ":"); - - if(key) - s = strcat(s, key.netname); - - GameLogEcho(s); -} - -vector kh_AttachedOrigin(entity e) // runs when a team captures the flag, it can run 2 or 3 times. -{ - if(e.tag_entity) - { - makevectors(e.tag_entity.angles); - return e.tag_entity.origin + e.origin.x * v_forward - e.origin.y * v_right + e.origin.z * v_up; - } - else - return e.origin; -} - -void kh_Key_Attach(entity key) // runs when a player picks up a key and several times when a key is assigned to a player at the start of a round -{ -#ifdef KH_PLAYER_USE_ATTACHMENT - entity first = key.owner.kh_next; - if(key == first) - { - setattachment(key, key.owner, KH_PLAYER_ATTACHMENT_BONE); - if(key.kh_next) - { - setattachment(key.kh_next, key, ""); - setorigin(key, key.kh_next.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST); - setorigin(key.kh_next, KH_PLAYER_ATTACHMENT_DIST_ROTATED); - key.kh_next.angles = '0 0 0'; - } - else - setorigin(key, KH_PLAYER_ATTACHMENT); - key.angles = KH_PLAYER_ATTACHMENT_ANGLES; - } - else - { - setattachment(key, key.kh_prev, ""); - if(key.kh_next) - setattachment(key.kh_next, key, ""); - setorigin(key, KH_PLAYER_ATTACHMENT_DIST_ROTATED); - setorigin(first, first.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST); - key.angles = '0 0 0'; - } -#else - setattachment(key, key.owner, ""); - setorigin(key, '0 0 1' * KH_KEY_ZSHIFT); // fixing x, y in think - key.angles_y -= key.owner.angles.y; -#endif - key.flags = 0; - if(IL_CONTAINS(g_items, key)) - IL_REMOVE(g_items, key); - key.solid = SOLID_NOT; - set_movetype(key, MOVETYPE_NONE); - key.team = key.owner.team; - key.nextthink = time; - key.damageforcescale = 0; - key.takedamage = DAMAGE_NO; - key.modelindex = kh_key_carried; - navigation_dynamicgoal_unset(key); -} - -void kh_Key_Detach(entity key) // runs every time a key is dropped or lost. Runs several times times when all the keys are captured -{ -#ifdef KH_PLAYER_USE_ATTACHMENT - entity first = key.owner.kh_next; - if(key == first) - { - if(key.kh_next) - { - setattachment(key.kh_next, key.owner, KH_PLAYER_ATTACHMENT_BONE); - setorigin(key.kh_next, key.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST); - key.kh_next.angles = KH_PLAYER_ATTACHMENT_ANGLES; - } - } - else - { - if(key.kh_next) - setattachment(key.kh_next, key.kh_prev, ""); - setorigin(first, first.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST); - } - // in any case: - setattachment(key, NULL, ""); - setorigin(key, key.owner.origin + '0 0 1' * (STAT(PL_MIN, key.owner).z - KH_KEY_MIN_z)); - key.angles = key.owner.angles; -#else - setorigin(key, key.owner.origin + key.origin.z * '0 0 1'); - setattachment(key, NULL, ""); - key.angles_y += key.owner.angles.y; -#endif - key.flags = FL_ITEM; - if(!IL_CONTAINS(g_items, key)) - IL_PUSH(g_items, key); - key.solid = SOLID_TRIGGER; - set_movetype(key, MOVETYPE_TOSS); - key.pain_finished = time + autocvar_g_balance_keyhunt_delay_return; - key.damageforcescale = autocvar_g_balance_keyhunt_damageforcescale; - key.takedamage = DAMAGE_YES; - // let key.team stay - key.modelindex = kh_key_dropped; - navigation_dynamicgoal_set(key); - key.kh_previous_owner = key.owner; - key.kh_previous_owner_playerid = key.owner.playerid; -} - -void kh_Key_AssignTo(entity key, entity player) // runs every time a key is picked up or assigned. Runs prior to kh_key_attach -{ - if(key.owner == player) - return; - - int ownerteam0 = kh_Key_AllOwnedByWhichTeam(); - - if(key.owner) - { - kh_Key_Detach(key); - - // remove from linked list - if(key.kh_next) - key.kh_next.kh_prev = key.kh_prev; - key.kh_prev.kh_next = key.kh_next; - key.kh_next = NULL; - key.kh_prev = NULL; - - if(key.owner.kh_next == NULL) - { - // No longer a key carrier - if(!kh_no_radar_circles) - WaypointSprite_Ping(key.owner.waypointsprite_attachedforcarrier); - WaypointSprite_DetachCarrier(key.owner); - } - } - - key.owner = player; - - if(player) - { - // insert into linked list - key.kh_next = player.kh_next; - key.kh_prev = player; - player.kh_next = key; - if(key.kh_next) - key.kh_next.kh_prev = key; - - float i; - i = kh_keystatus[key.owner.playerid]; - if(key.netname == "^1red key") - i += 1; - if(key.netname == "^4blue key") - i += 2; - if(key.netname == "^3yellow key") - i += 4; - if(key.netname == "^6pink key") - i += 8; - kh_keystatus[key.owner.playerid] = i; - - kh_Key_Attach(key); - - if(key.kh_next == NULL) - { - // player is now a key carrier - entity wp = WaypointSprite_AttachCarrier(WP_Null, player, RADARICON_FLAGCARRIER); - wp.colormod = colormapPaletteColor(player.team - 1, 0); - player.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_KeyCarrier_waypointsprite_visible_for_player; - WaypointSprite_UpdateRule(player.waypointsprite_attachedforcarrier, player.team, SPRITERULE_TEAMPLAY); - if(player.team == NUM_TEAM_1) - WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierRed, WP_KeyCarrierFriend, WP_KeyCarrierRed); - else if(player.team == NUM_TEAM_2) - WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierBlue, WP_KeyCarrierFriend, WP_KeyCarrierBlue); - else if(player.team == NUM_TEAM_3) - WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierYellow, WP_KeyCarrierFriend, WP_KeyCarrierYellow); - else if(player.team == NUM_TEAM_4) - WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierPink, WP_KeyCarrierFriend, WP_KeyCarrierPink); - if(!kh_no_radar_circles) - WaypointSprite_Ping(player.waypointsprite_attachedforcarrier); - } - } - - // moved that here, also update if there's no player - kh_update_state(); - - key.pusher = NULL; - - int ownerteam = kh_Key_AllOwnedByWhichTeam(); - if(ownerteam != ownerteam0) - { - entity k; - if(ownerteam != -1) - { - kh_interferemsg_time = time + 0.2; - kh_interferemsg_team = player.team; - - // audit all key carrier sprites, update them to "Run here" - FOR_EACH_KH_KEY(k) - { - if (!k.owner) continue; - entity first = WP_Null; - FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, { first = it; break; }); - entity third = WP_Null; - FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { third = it; break; }); - WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFinish, third); - } - } - else - { - kh_interferemsg_time = 0; - - // audit all key carrier sprites, update them to "Key Carrier" - FOR_EACH_KH_KEY(k) - { - if (!k.owner) continue; - entity first = WP_Null; - FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, { first = it; break; }); - entity third = WP_Null; - FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { third = it; break; }); - WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFriend, third); - } - } - } -} - -void kh_Key_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) -{ - if(this.owner) - return; - if(ITEM_DAMAGE_NEEDKILL(deathtype)) - { - this.pain_finished = bound(time, time + autocvar_g_balance_keyhunt_delay_damage_return, this.pain_finished); - return; - } - if(force == '0 0 0') - return; - if(time > this.pushltime) - if(IS_PLAYER(attacker)) - this.team = attacker.team; -} - -void kh_Key_Collect(entity key, entity player) //a player picks up a dropped key -{ - sound(player, CH_TRIGGER, SND_KH_COLLECT, VOL_BASE, ATTEN_NORM); - - if(key.kh_dropperteam != player.team) - { - kh_Scores_Event(player, key, "collect", autocvar_g_balance_keyhunt_score_collect, 0); - GameRules_scoring_add(player, KH_PICKUPS, 1); - } - key.kh_dropperteam = 0; - int realteam = kh_Team_ByID(key.count); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PICKUP), player.netname); - - kh_Key_AssignTo(key, player); // this also updates .kh_state -} - -void kh_Key_Touch(entity this, entity toucher) // runs many, many times when a key has been dropped and can be picked up -{ - if(game_stopped) - return; - - if(this.owner) // already carried - return; - - if(ITEM_TOUCH_NEEDKILL()) - { - this.pain_finished = bound(time, time + autocvar_g_balance_keyhunt_delay_damage_return, this.pain_finished); - return; - } - - if (!IS_PLAYER(toucher)) - return; - if(IS_DEAD(toucher)) - return; - if(toucher == this.enemy) - if(time < this.kh_droptime + autocvar_g_balance_keyhunt_delay_collect) - return; // you just dropped it! - kh_Key_Collect(this, toucher); -} - -void kh_Key_Remove(entity key) // runs after when all the keys have been collected or when a key has been dropped for more than X seconds -{ - entity o = key.owner; - kh_Key_AssignTo(key, NULL); - if(o) // it was attached - WaypointSprite_Kill(key.waypointsprite_attachedforcarrier); - else // it was dropped - WaypointSprite_DetachCarrier(key); - - // remove key from key list - if (kh_worldkeylist == key) - kh_worldkeylist = kh_worldkeylist.kh_worldkeynext; - else - { - o = kh_worldkeylist; - while (o) - { - if (o.kh_worldkeynext == key) - { - o.kh_worldkeynext = o.kh_worldkeynext.kh_worldkeynext; - break; - } - o = o.kh_worldkeynext; - } - } - - delete(key); - - kh_update_state(); -} - -void kh_FinishRound() // runs when a team captures the keys -{ - // prepare next round - kh_interferemsg_time = 0; - entity key; - - kh_no_radar_circles = true; - FOR_EACH_KH_KEY(key) - kh_Key_Remove(key); - kh_no_radar_circles = false; - - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round); - kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound); -} - -void nades_GiveBonus(entity player, float score); - -void kh_WinnerTeam(int winner_team) // runs when a team wins -{ - // all key carriers get some points - entity key; - float score = (NumTeams(kh_teams) - 1) * autocvar_g_balance_keyhunt_score_capture; - DistributeEvenly_Init(score, NumTeams(kh_teams)); - // twice the score for 3 team games, three times the score for 4 team games! - // note: for a win by destroying the key, this should NOT be applied - FOR_EACH_KH_KEY(key) - { - float f = DistributeEvenly_Get(1); - kh_Scores_Event(key.owner, key, "capture", f, 0); - GameRules_scoring_add_team(key.owner, KH_CAPS, 1); - nades_GiveBonus(key.owner, autocvar_g_nades_bonus_score_high); - } - - bool first = true; - string keyowner = ""; - FOR_EACH_KH_KEY(key) - if(key.owner.kh_next == key) - { - if(!first) - keyowner = strcat(keyowner, ", "); - keyowner = key.owner.netname; - first = false; - } - - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN)); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_KEYHUNT_CAPTURE), keyowner); - - first = true; - vector firstorigin = '0 0 0', lastorigin = '0 0 0', midpoint = '0 0 0'; - FOR_EACH_KH_KEY(key) - { - vector thisorigin = kh_AttachedOrigin(key); - //dprint("Key origin: ", vtos(thisorigin), "\n"); - midpoint += thisorigin; - - if(!first) - te_lightning2(NULL, lastorigin, thisorigin); - lastorigin = thisorigin; - if(first) - firstorigin = thisorigin; - first = false; - } - if(NumTeams(kh_teams) > 2) - { - te_lightning2(NULL, lastorigin, firstorigin); - } - midpoint = midpoint * (1 / NumTeams(kh_teams)); - te_customflash(midpoint, 1000, 1, Team_ColorRGB(winner_team) * 0.5 + '0.5 0.5 0.5'); // make the color >=0.5 in each component - - play2all(SND(KH_CAPTURE)); - kh_FinishRound(); -} - -void kh_LoserTeam(int loser_team, entity lostkey) // runs when a player pushes a flag carrier off the map -{ - float f; - entity attacker = NULL; - if(lostkey.pusher) - if(lostkey.pusher.team != loser_team) - if(IS_PLAYER(lostkey.pusher)) - attacker = lostkey.pusher; - - if(attacker) - { - if(lostkey.kh_previous_owner) - kh_Scores_Event(lostkey.kh_previous_owner, NULL, "pushed", 0, -autocvar_g_balance_keyhunt_score_push); - // don't actually GIVE him the -nn points, just log - kh_Scores_Event(attacker, NULL, "push", autocvar_g_balance_keyhunt_score_push, 0); - GameRules_scoring_add(attacker, KH_PUSHES, 1); - //centerprint(attacker, "Your push is the best!"); // does this really need to exist? - } - else - { - int players = 0; - float of = autocvar_g_balance_keyhunt_score_destroyed_ownfactor; - - FOREACH_CLIENT(IS_PLAYER(it) && it.team != loser_team, { ++players; }); - - entity key; - int keys = 0; - FOR_EACH_KH_KEY(key) - if(key.owner && key.team != loser_team) - ++keys; - - if(lostkey.kh_previous_owner) - kh_Scores_Event(lostkey.kh_previous_owner, NULL, "destroyed", 0, -autocvar_g_balance_keyhunt_score_destroyed); - // don't actually GIVE him the -nn points, just log - - if(lostkey.kh_previous_owner.playerid == lostkey.kh_previous_owner_playerid) - GameRules_scoring_add(lostkey.kh_previous_owner, KH_DESTROYS, 1); - - DistributeEvenly_Init(autocvar_g_balance_keyhunt_score_destroyed, keys * of + players); - - FOR_EACH_KH_KEY(key) - if(key.owner && key.team != loser_team) - { - f = DistributeEvenly_Get(of); - kh_Scores_Event(key.owner, NULL, "destroyed_holdingkey", f, 0); - } - - int fragsleft = DistributeEvenly_Get(players); - - // Now distribute these among all other teams... - int j = NumTeams(kh_teams) - 1; - for(int i = 0; i < NumTeams(kh_teams); ++i) - { - int thisteam = kh_Team_ByID(i); - if(thisteam == loser_team) // bad boy, no cookie - this WILL happen - continue; - - players = 0; - FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, { ++players; }); - - DistributeEvenly_Init(fragsleft, j); - fragsleft = DistributeEvenly_Get(j - 1); - DistributeEvenly_Init(DistributeEvenly_Get(1), players); - - FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, { - f = DistributeEvenly_Get(1); - kh_Scores_Event(it, NULL, "destroyed", f, 0); - }); - - --j; - } - } - - int realteam = kh_Team_ByID(lostkey.count); - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(loser_team, CENTER_ROUND_TEAM_LOSS)); - if(attacker) - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PUSHED), attacker.netname, lostkey.kh_previous_owner.netname); - else - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DESTROYED), lostkey.kh_previous_owner.netname); - - play2all(SND(KH_DESTROY)); - te_tarexplosion(lostkey.origin); - - kh_FinishRound(); -} - -void kh_Key_Think(entity this) // runs all the time -{ - if(game_stopped) - return; - - if(this.owner) - { -#ifndef KH_PLAYER_USE_ATTACHMENT - makevectors('0 1 0' * (this.cnt + (time % 360) * KH_KEY_XYSPEED)); - setorigin(this, v_forward * KH_KEY_XYDIST + '0 0 1' * this.origin.z); -#endif - } - - // if in nodrop or time over, end the round - if(!this.owner) - if(time > this.pain_finished) - kh_LoserTeam(this.team, this); - - if(this.owner) - if(kh_Key_AllOwnedByWhichTeam() != -1) - { - if(this.siren_time < time) - { - sound(this.owner, CH_TRIGGER, SND_KH_ALARM, VOL_BASE, ATTEN_NORM); // play a simple alarm - this.siren_time = time + 2.5; // repeat every 2.5 seconds - } - - entity key; - vector p = this.owner.origin; - FOR_EACH_KH_KEY(key) - if(vdist(key.owner.origin - p, >, autocvar_g_balance_keyhunt_maxdist)) - goto not_winning; - kh_WinnerTeam(this.team); -LABEL(not_winning) - } - - if(kh_interferemsg_time && time > kh_interferemsg_time) - { - kh_interferemsg_time = 0; - FOREACH_CLIENT(IS_PLAYER(it), { - if(it.team == kh_interferemsg_team) - if(it.kh_next) - Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_MEET); - else - Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_HELP); - else - Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_TEAM_NUM(kh_interferemsg_team, CENTER_KEYHUNT_INTERFERE)); - }); - } - - this.nextthink = time + 0.05; -} - -void key_reset(entity this) -{ - kh_Key_AssignTo(this, NULL); - kh_Key_Remove(this); -} - -const string STR_ITEM_KH_KEY = "item_kh_key"; -void kh_Key_Spawn(entity initial_owner, float _angle, float i) // runs every time a new flag is created, ie after all the keys have been collected -{ - entity key = spawn(); - key.count = i; - key.classname = STR_ITEM_KH_KEY; - settouch(key, kh_Key_Touch); - setthink(key, kh_Key_Think); - key.nextthink = time; - key.items = IT_KEY1 | IT_KEY2; - key.cnt = _angle; - key.angles = '0 360 0' * random(); - key.event_damage = kh_Key_Damage; - key.takedamage = DAMAGE_YES; - key.damagedbytriggers = autocvar_g_balance_keyhunt_return_when_unreachable; - key.damagedbycontents = autocvar_g_balance_keyhunt_return_when_unreachable; - key.modelindex = kh_key_dropped; - key.model = "key"; - key.kh_dropperteam = 0; - key.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; - setsize(key, KH_KEY_MIN, KH_KEY_MAX); - key.colormod = Team_ColorRGB(initial_owner.team) * KH_KEY_BRIGHTNESS; - key.reset = key_reset; - navigation_dynamicgoal_init(key, false); - - switch(initial_owner.team) - { - case NUM_TEAM_1: - key.netname = "^1red key"; - break; - case NUM_TEAM_2: - key.netname = "^4blue key"; - break; - case NUM_TEAM_3: - key.netname = "^3yellow key"; - break; - case NUM_TEAM_4: - key.netname = "^6pink key"; - break; - default: - key.netname = "NETGIER key"; - break; - } - - // link into key list - key.kh_worldkeynext = kh_worldkeylist; - kh_worldkeylist = key; - - Send_Notification(NOTIF_ONE, initial_owner, MSG_CENTER, APP_TEAM_NUM(initial_owner.team, CENTER_KEYHUNT_START)); - - WaypointSprite_Spawn(WP_KeyDropped, 0, 0, key, '0 0 1' * KH_KEY_WP_ZSHIFT, NULL, key.team, key, waypointsprite_attachedforcarrier, false, RADARICON_FLAG); - key.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_Key_waypointsprite_visible_for_player; - - kh_Key_AssignTo(key, initial_owner); -} - -// -1 when no team completely owns all keys yet -int kh_Key_AllOwnedByWhichTeam() // constantly called. check to see if all the keys are owned by the same team -{ - entity key; - int teem = -1; - int keys = NumTeams(kh_teams); - FOR_EACH_KH_KEY(key) - { - if(!key.owner) - return -1; - if(teem == -1) - teem = key.team; - else if(teem != key.team) - return -1; - --keys; - } - if(keys != 0) - return -1; - return teem; -} - -void kh_Key_DropOne(entity key) -{ - // prevent collecting this one for some time - entity player = key.owner; - - key.kh_droptime = time; - key.enemy = player; - - kh_Scores_Event(player, key, "dropkey", 0, 0); - GameRules_scoring_add(player, KH_LOSSES, 1); - int realteam = kh_Team_ByID(key.count); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DROP), player.netname); - - kh_Key_AssignTo(key, NULL); - makevectors(player.v_angle); - key.velocity = W_CalculateProjectileVelocity(player, player.velocity, autocvar_g_balance_keyhunt_throwvelocity * v_forward, false); - key.pusher = NULL; - key.pushltime = time + autocvar_g_balance_keyhunt_protecttime; - key.kh_dropperteam = key.team; - - sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM); -} - -void kh_Key_DropAll(entity player, float suicide) // runs whenever a player dies -{ - if(player.kh_next) - { - entity mypusher = NULL; - if(player.pusher) - if(time < player.pushltime) - mypusher = player.pusher; - - entity key; - while((key = player.kh_next)) - { - kh_Scores_Event(player, key, "losekey", 0, 0); - GameRules_scoring_add(player, KH_LOSSES, 1); - int realteam = kh_Team_ByID(key.count); - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_LOST), player.netname); - kh_Key_AssignTo(key, NULL); - makevectors('-1 0 0' * (45 + 45 * random()) + '0 360 0' * random()); - key.velocity = W_CalculateProjectileVelocity(player, player.velocity, autocvar_g_balance_keyhunt_dropvelocity * v_forward, false); - key.pusher = mypusher; - key.pushltime = time + autocvar_g_balance_keyhunt_protecttime; - if(suicide) - key.kh_dropperteam = player.team; - } - sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM); - } -} - -int kh_GetMissingTeams() -{ - int missing_teams = 0; - for(int i = 0; i < NumTeams(kh_teams); ++i) - { - int teem = kh_Team_ByID(i); - int players = 0; - FOREACH_CLIENT(IS_PLAYER(it), { - if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem) - ++players; - }); - if (!players) - missing_teams |= (2 ** i); - } - return missing_teams; -} - -void kh_WaitForPlayers() // delay start of the round until enough players are present -{ - static int prev_missing_teams_mask; - if(time < game_starttime) - { - if (prev_missing_teams_mask > 0) - Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS); - prev_missing_teams_mask = -1; - kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers); - return; - } - - int missing_teams_mask = kh_GetMissingTeams(); - if(!missing_teams_mask) - { - if(prev_missing_teams_mask > 0) - Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS); - prev_missing_teams_mask = -1; - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round); - kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound); - } - else - { - if(player_count == 0) - { - if(prev_missing_teams_mask > 0) - Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS); - prev_missing_teams_mask = -1; - } - else - { - if(prev_missing_teams_mask != missing_teams_mask) - { - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask); - prev_missing_teams_mask = missing_teams_mask; - } - } - kh_Controller_SetThink(1, kh_WaitForPlayers); - } -} - -void kh_EnableTrackingDevice() // runs after each round -{ - Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT); - Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER); - - kh_tracking_enabled = true; -} - -void kh_StartRound() // runs at the start of each round -{ - if(time < game_starttime) - { - kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers); - return; - } - - if(kh_GetMissingTeams()) - { - kh_Controller_SetThink(1, kh_WaitForPlayers); - return; - } - - Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT); - Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER); - - for(int i = 0; i < NumTeams(kh_teams); ++i) - { - int teem = kh_Team_ByID(i); - int players = 0; - entity my_player = NULL; - FOREACH_CLIENT(IS_PLAYER(it), { - if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem) - { - ++players; - if(random() * players <= 1) - my_player = it; - } - }); - kh_Key_Spawn(my_player, 360 * i / NumTeams(kh_teams), i); - } - - kh_tracking_enabled = false; - Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEYHUNT_SCAN, autocvar_g_balance_keyhunt_delay_tracking); - kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_tracking, kh_EnableTrackingDevice); -} - -float kh_HandleFrags(entity attacker, entity targ, float f) // adds to the player score -{ - if(attacker == targ) - return f; - - if(targ.kh_next) - { - if(attacker.team == targ.team) - { - int nk = 0; - for(entity k = targ.kh_next; k != NULL; k = k.kh_next) - ++nk; - kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", -nk * autocvar_g_balance_keyhunt_score_collect, 0); - } - else - { - kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", autocvar_g_balance_keyhunt_score_carrierfrag-1, 0); - GameRules_scoring_add(attacker, KH_KCKILLS, 1); - // the frag gets added later - } - } - - return f; -} - -void kh_Initialize() // sets up th KH environment -{ - // setup variables - kh_teams = autocvar_g_keyhunt_teams_override; - if(kh_teams < 2) - kh_teams = cvar("g_keyhunt_teams"); // read the cvar directly as it gets written earlier in the same frame - kh_teams = BITS(bound(2, kh_teams, 4)); - - // make a KH entity for controlling the game - kh_controller = spawn(); - setthink(kh_controller, kh_Controller_Think); - kh_Controller_SetThink(0, kh_WaitForPlayers); - - setmodel(kh_controller, MDL_KH_KEY); - kh_key_dropped = kh_controller.modelindex; - /* - dprint(vtos(kh_controller.mins)); - dprint(vtos(kh_controller.maxs)); - dprint("\n"); - */ -#ifdef KH_PLAYER_USE_CARRIEDMODEL - setmodel(kh_controller, MDL_KH_KEY_CARRIED); - kh_key_carried = kh_controller.modelindex; -#else - kh_key_carried = kh_key_dropped; -#endif - - kh_controller.model = ""; - kh_controller.modelindex = 0; - - kh_ScoreRules(kh_teams); -} - -void kh_finalize() -{ - // to be called before intermission - kh_FinishRound(); - delete(kh_controller); - kh_controller = NULL; -} - -// legacy bot role - -void(entity this) havocbot_role_kh_carrier; -void(entity this) havocbot_role_kh_defense; -void(entity this) havocbot_role_kh_offense; -void(entity this) havocbot_role_kh_freelancer; - - -void havocbot_goalrating_kh(entity this, float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy) -{ - entity head; - for (head = kh_worldkeylist; head; head = head.kh_worldkeynext) - { - if(head.owner == this) - continue; - if(!kh_tracking_enabled) - { - // if it's carried by our team we know about it - // otherwise we have to see it to know about it - if(!head.owner || head.team != this.team) - { - traceline(this.origin + this.view_ofs, head.origin, MOVE_NOMONSTERS, this); - if (trace_fraction < 1 && trace_ent != head) - continue; // skip what I can't see - } - } - if(!head.owner) - navigation_routerating(this, head, ratingscale_dropped * 10000, 100000); - else if(head.team == this.team) - navigation_routerating(this, head.owner, ratingscale_team * 10000, 100000); - else - navigation_routerating(this, head.owner, ratingscale_enemy * 10000, 100000); - } - - havocbot_goalrating_items(this, 1, this.origin, 10000); -} - -void havocbot_role_kh_carrier(entity this) -{ - if(IS_DEAD(this)) - return; - - if (!(this.kh_next)) - { - LOG_TRACE("changing role to freelancer"); - this.havocbot_role = havocbot_role_kh_freelancer; - this.havocbot_role_timeout = 0; - return; - } - - if (navigation_goalrating_timeout(this)) - { - navigation_goalrating_start(this); - - if(kh_Key_AllOwnedByWhichTeam() == this.team) - havocbot_goalrating_kh(this, 10, 0.1, 0.1); // bring home - else - havocbot_goalrating_kh(this, 4, 4, 1); // play defensively - - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - } -} - -void havocbot_role_kh_defense(entity this) -{ - if(IS_DEAD(this)) - return; - - if (this.kh_next) - { - LOG_TRACE("changing role to carrier"); - this.havocbot_role = havocbot_role_kh_carrier; - this.havocbot_role_timeout = 0; - return; - } - - if (!this.havocbot_role_timeout) - this.havocbot_role_timeout = time + random() * 10 + 20; - if (time > this.havocbot_role_timeout) - { - LOG_TRACE("changing role to freelancer"); - this.havocbot_role = havocbot_role_kh_freelancer; - this.havocbot_role_timeout = 0; - return; - } - - if (navigation_goalrating_timeout(this)) - { - float key_owner_team; - navigation_goalrating_start(this); - - key_owner_team = kh_Key_AllOwnedByWhichTeam(); - if(key_owner_team == this.team) - havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend key carriers - else if(key_owner_team == -1) - havocbot_goalrating_kh(this, 4, 1, 0.1); // play defensively - else - havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY - - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - } -} - -void havocbot_role_kh_offense(entity this) -{ - if(IS_DEAD(this)) - return; - - if (this.kh_next) - { - LOG_TRACE("changing role to carrier"); - this.havocbot_role = havocbot_role_kh_carrier; - this.havocbot_role_timeout = 0; - return; - } - - if (!this.havocbot_role_timeout) - this.havocbot_role_timeout = time + random() * 10 + 20; - if (time > this.havocbot_role_timeout) - { - LOG_TRACE("changing role to freelancer"); - this.havocbot_role = havocbot_role_kh_freelancer; - this.havocbot_role_timeout = 0; - return; - } - - if (navigation_goalrating_timeout(this)) - { - float key_owner_team; - - navigation_goalrating_start(this); - - key_owner_team = kh_Key_AllOwnedByWhichTeam(); - if(key_owner_team == this.team) - havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway - else if(key_owner_team == -1) - havocbot_goalrating_kh(this, 0.1, 1, 4); // play offensively - else - havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK! EMERGENCY! - - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - } -} - -void havocbot_role_kh_freelancer(entity this) -{ - if(IS_DEAD(this)) - return; - - if (this.kh_next) - { - LOG_TRACE("changing role to carrier"); - this.havocbot_role = havocbot_role_kh_carrier; - this.havocbot_role_timeout = 0; - return; - } - - if (!this.havocbot_role_timeout) - this.havocbot_role_timeout = time + random() * 10 + 10; - if (time > this.havocbot_role_timeout) - { - if (random() < 0.5) - { - LOG_TRACE("changing role to offense"); - this.havocbot_role = havocbot_role_kh_offense; - } - else - { - LOG_TRACE("changing role to defense"); - this.havocbot_role = havocbot_role_kh_defense; - } - this.havocbot_role_timeout = 0; - return; - } - - if (navigation_goalrating_timeout(this)) - { - navigation_goalrating_start(this); - - int key_owner_team = kh_Key_AllOwnedByWhichTeam(); - if(key_owner_team == this.team) - havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway - else if(key_owner_team == -1) - havocbot_goalrating_kh(this, 1, 10, 4); // prefer dropped keys - else - havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY - - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - } -} - - -// register this as a mutator - -MUTATOR_HOOKFUNCTION(kh, ClientDisconnect) -{ - entity player = M_ARGV(0, entity); - - kh_Key_DropAll(player, true); -} - -MUTATOR_HOOKFUNCTION(kh, MakePlayerObserver) -{ - entity player = M_ARGV(0, entity); - - kh_Key_DropAll(player, true); -} - -MUTATOR_HOOKFUNCTION(kh, PlayerDies) -{ - entity frag_attacker = M_ARGV(1, entity); - entity frag_target = M_ARGV(2, entity); - - if(frag_target == frag_attacker) - kh_Key_DropAll(frag_target, true); - else if(IS_PLAYER(frag_attacker)) - kh_Key_DropAll(frag_target, false); - else - kh_Key_DropAll(frag_target, true); -} - -MUTATOR_HOOKFUNCTION(kh, GiveFragsForKill, CBC_ORDER_FIRST) -{ - entity frag_attacker = M_ARGV(0, entity); - entity frag_target = M_ARGV(1, entity); - float frag_score = M_ARGV(2, float); - M_ARGV(2, float) = kh_HandleFrags(frag_attacker, frag_target, frag_score); -} - -MUTATOR_HOOKFUNCTION(kh, MatchEnd) -{ - kh_finalize(); -} - -MUTATOR_HOOKFUNCTION(kh, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE) -{ - M_ARGV(0, float) = kh_teams; -} - -MUTATOR_HOOKFUNCTION(kh, SpectateCopy) -{ - entity spectatee = M_ARGV(0, entity); - entity client = M_ARGV(1, entity); - - STAT(KH_KEYS, client) = STAT(KH_KEYS, spectatee); -} - -MUTATOR_HOOKFUNCTION(kh, PlayerUseKey) -{ - entity player = M_ARGV(0, entity); - - if(MUTATOR_RETURNVALUE == 0) - { - entity k = player.kh_next; - if(k) - { - kh_Key_DropOne(k); - return true; - } - } -} - -MUTATOR_HOOKFUNCTION(kh, HavocBot_ChooseRole) -{ - entity bot = M_ARGV(0, entity); - - if(IS_DEAD(bot)) - return true; - - float r = random() * 3; - if (r < 1) - bot.havocbot_role = havocbot_role_kh_offense; - else if (r < 2) - bot.havocbot_role = havocbot_role_kh_defense; - else - bot.havocbot_role = havocbot_role_kh_freelancer; - - return true; -} - -MUTATOR_HOOKFUNCTION(kh, DropSpecialItems) -{ - entity frag_target = M_ARGV(0, entity); - - kh_Key_DropAll(frag_target, false); -} - -MUTATOR_HOOKFUNCTION(kh, reset_map_global) -{ - kh_WaitForPlayers(); // takes care of killing the "missing teams" message -} diff --git a/qcsrc/server/mutators/mutator/gamemode_keyhunt.qh b/qcsrc/server/mutators/mutator/gamemode_keyhunt.qh deleted file mode 100644 index 77d7c06fc..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_keyhunt.qh +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include "../gamemode.qh" - -#define autocvar_g_keyhunt_point_limit cvar("g_keyhunt_point_limit") -int autocvar_g_keyhunt_point_leadlimit; -bool autocvar_g_keyhunt_team_spawns; -void kh_Initialize(); - -REGISTER_MUTATOR(kh, false) -{ - MUTATOR_STATIC(); - MUTATOR_ONADD - { - GameRules_teams(true); - GameRules_spawning_teams(autocvar_g_keyhunt_team_spawns); - GameRules_limit_score(autocvar_g_keyhunt_point_limit); - GameRules_limit_lead(autocvar_g_keyhunt_point_leadlimit); - - kh_Initialize(); - } - return 0; -} - -#define FOR_EACH_KH_KEY(v) for(v = kh_worldkeylist; v; v = v.kh_worldkeynext ) - -// ALL OF THESE should be removed in the future, as other code should not have to care - -// used by bots: -bool kh_tracking_enabled; -.entity kh_next; - -USING(kh_Think_t, void()); -void kh_StartRound(); -void kh_Controller_SetThink(float t, kh_Think_t func); diff --git a/qcsrc/server/mutators/mutator/gamemode_lms.qc b/qcsrc/server/mutators/mutator/gamemode_lms.qc deleted file mode 100644 index a57b2ae2d..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_lms.qc +++ /dev/null @@ -1,429 +0,0 @@ -#include "gamemode_lms.qh" - -#include -#include -#include - -int autocvar_g_lms_extra_lives; -bool autocvar_g_lms_join_anytime; -int autocvar_g_lms_last_join; -bool autocvar_g_lms_regenerate; - -// main functions -float LMS_NewPlayerLives() -{ - float fl; - fl = autocvar_fraglimit; - if(fl == 0) - fl = 999; - - // first player has left the game for dying too much? Nobody else can get in. - if(lms_lowest_lives < 1) - return 0; - - if(!autocvar_g_lms_join_anytime) - if(lms_lowest_lives < fl - autocvar_g_lms_last_join) - return 0; - - return bound(1, lms_lowest_lives, fl); -} - -void ClearWinners(); - -// LMS winning condition: game terminates if and only if there's at most one -// one player who's living lives. Top two scores being equal cancels the time -// limit. -int WinningCondition_LMS() -{ - entity first_player = NULL; - int total_players = 0; - FOREACH_CLIENT(IS_PLAYER(it), { - if (!total_players) - first_player = it; - ++total_players; - }); - - if (total_players) - { - if (total_players > 1) - { - // two or more active players - continue with the game - - if (autocvar_g_campaign) - { - FOREACH_CLIENT(IS_REAL_CLIENT(it), { - float pl_lives = GameRules_scoring_add(it, LMS_LIVES, 0); - if (!pl_lives) - return WINNING_YES; // human player lost, game over - break; - }); - } - } - else - { - // exactly one player? - - ClearWinners(); - SetWinners(winning, 0); // NOTE: exactly one player is still "player", so this works out - - if (LMS_NewPlayerLives()) - { - // game still running (that is, nobody got removed from the game by a frag yet)? then continue - return WINNING_NO; - } - else - { - // a winner! - // and assign him his first place - GameRules_scoring_add(first_player, LMS_RANK, 1); - if(warmup_stage) - return WINNING_NO; - else - return WINNING_YES; - } - } - } - else - { - // nobody is playing at all... - if (LMS_NewPlayerLives()) - { - // wait for players... - } - else - { - // SNAFU (maybe a draw game?) - ClearWinners(); - LOG_TRACE("No players, ending game."); - return WINNING_YES; - } - } - - // When we get here, we have at least two players who are actually LIVING, - // now check if the top two players have equal score. - WinningConditionHelper(NULL); - - ClearWinners(); - if(WinningConditionHelper_winner) - WinningConditionHelper_winner.winning = true; - if(WinningConditionHelper_topscore == WinningConditionHelper_secondscore) - return WINNING_NEVER; - - // Top two have different scores? Way to go for our beloved TIMELIMIT! - return WINNING_NO; -} - -// mutator hooks -MUTATOR_HOOKFUNCTION(lms, reset_map_global) -{ - lms_lowest_lives = 999; -} - -MUTATOR_HOOKFUNCTION(lms, reset_map_players) -{ - FOREACH_CLIENT(true, { - TRANSMUTE(Player, it); - it.frags = FRAGS_PLAYER; - GameRules_scoring_add(it, LMS_LIVES, LMS_NewPlayerLives()); - PutClientInServer(it); - }); -} - -MUTATOR_HOOKFUNCTION(lms, PutClientInServer) -{ - entity player = M_ARGV(0, entity); - - if(player.frags == FRAGS_SPECTATOR) - TRANSMUTE(Observer, player); - else - { - float tl = GameRules_scoring_add(player, LMS_LIVES, 0); - if(tl < lms_lowest_lives) - lms_lowest_lives = tl; - if(tl <= 0) - TRANSMUTE(Observer, player); - if(warmup_stage) - GameRules_scoring_add(player, LMS_RANK, -GameRules_scoring_add(player, LMS_RANK, 0)); - } -} - -MUTATOR_HOOKFUNCTION(lms, ForbidSpawn) -{ - entity player = M_ARGV(0, entity); - - if(warmup_stage) - return false; - if(player.frags == FRAGS_SPECTATOR) - return true; - if(GameRules_scoring_add(player, LMS_LIVES, 0) <= 0) - { - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_LMS_NOLIVES); - return true; - } - return false; -} - -MUTATOR_HOOKFUNCTION(lms, PlayerDies) -{ - entity frag_target = M_ARGV(2, entity); - - frag_target.respawn_flags |= RESPAWN_FORCE; -} - -void lms_RemovePlayer(entity player) -{ - static int quitters = 0; - float player_rank = GameRules_scoring_add(player, LMS_RANK, 0); - if (!player_rank) - { - int pl_cnt = 0; - FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; }); - if (player.lms_spectate_warning != 2) - { - if(IS_BOT_CLIENT(player)) - bot_clear(player); - player.frags = FRAGS_LMS_LOSER; - GameRules_scoring_add(player, LMS_RANK, pl_cnt + 1); - } - else - { - lms_lowest_lives = 999; - FOREACH_CLIENT(true, { - if (it.frags == FRAGS_LMS_LOSER) - { - float it_rank = GameRules_scoring_add(it, LMS_RANK, 0); - if (it_rank > player_rank && it_rank <= 256) - GameRules_scoring_add(it, LMS_RANK, -1); - lms_lowest_lives = 0; - } - else if (it.frags != FRAGS_SPECTATOR) - { - float tl = GameRules_scoring_add(it, LMS_LIVES, 0); - if(tl < lms_lowest_lives) - lms_lowest_lives = tl; - } - }); - GameRules_scoring_add(player, LMS_RANK, 665 - quitters); // different from 666 - if(!warmup_stage) - { - GameRules_scoring_add(player, LMS_LIVES, -GameRules_scoring_add(player, LMS_LIVES, 0)); - ++quitters; - } - player.frags = FRAGS_LMS_LOSER; - TRANSMUTE(Observer, player); - } - if (pl_cnt == 2 && !warmup_stage) // a player is forfeiting leaving only one player - lms_lowest_lives = 0; // end the game now! - } - - if(CS(player).killcount != FRAGS_SPECTATOR) - if(GameRules_scoring_add(player, LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2) - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_NOLIVES, player.netname); - else - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_FORFEIT, player.netname); -} - -MUTATOR_HOOKFUNCTION(lms, ClientDisconnect) -{ - entity player = M_ARGV(0, entity); - - lms_RemovePlayer(player); -} - -MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver) -{ - entity player = M_ARGV(0, entity); - - lms_RemovePlayer(player); - return true; // prevent team reset -} - -MUTATOR_HOOKFUNCTION(lms, ClientConnect) -{ - entity player = M_ARGV(0, entity); - - TRANSMUTE(Player, player); - campaign_bots_may_start = true; - - if(GameRules_scoring_add(player, LMS_LIVES, LMS_NewPlayerLives()) <= 0) - { - GameRules_scoring_add(player, LMS_RANK, 666); // mark as forced spectator for the hud code - player.frags = FRAGS_SPECTATOR; - } -} - -MUTATOR_HOOKFUNCTION(lms, PlayerPreThink) -{ - entity player = M_ARGV(0, entity); - - if(player.deadflag == DEAD_DYING) - player.deadflag = DEAD_RESPAWNING; -} - -MUTATOR_HOOKFUNCTION(lms, PlayerRegen) -{ - if(autocvar_g_lms_regenerate) - return false; - return true; -} - -MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon) -{ - // forbode! - return true; -} - -MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill) -{ - entity frag_target = M_ARGV(1, entity); - - if (!warmup_stage) - { - // remove a life - int tl = GameRules_scoring_add(frag_target, LMS_LIVES, -1); - if(tl < lms_lowest_lives) - lms_lowest_lives = tl; - if(tl <= 0) - { - int pl_cnt = 0; - FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; }); - if(IS_BOT_CLIENT(frag_target)) - bot_clear(frag_target); - frag_target.frags = FRAGS_LMS_LOSER; - GameRules_scoring_add(frag_target, LMS_RANK, pl_cnt); - } - } - M_ARGV(2, float) = 0; // frag score - - return true; -} - -MUTATOR_HOOKFUNCTION(lms, SetStartItems) -{ - start_items &= ~IT_UNLIMITED_AMMO; - start_health = warmup_start_health = cvar("g_lms_start_health"); - start_armorvalue = warmup_start_armorvalue = cvar("g_lms_start_armor"); - start_ammo_shells = warmup_start_ammo_shells = cvar("g_lms_start_ammo_shells"); - start_ammo_nails = warmup_start_ammo_nails = cvar("g_lms_start_ammo_nails"); - start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets"); - start_ammo_cells = warmup_start_ammo_cells = cvar("g_lms_start_ammo_cells"); - start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_lms_start_ammo_plasma"); - start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_lms_start_ammo_fuel"); -} - -MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear) -{ - // don't clear player score - return true; -} - -MUTATOR_HOOKFUNCTION(lms, FilterItemDefinition) -{ - entity definition = M_ARGV(0, entity); - - if (autocvar_g_lms_extra_lives && definition == ITEM_ExtraLife) - { - return false; - } - return true; -} - -void lms_extralife(entity this) -{ - StartItem(this, ITEM_ExtraLife); -} - -MUTATOR_HOOKFUNCTION(lms, OnEntityPreSpawn) -{ - if (!autocvar_g_powerups) return false; - if (!autocvar_g_lms_extra_lives) return false; - - entity ent = M_ARGV(0, entity); - - // Can't use .itemdef here - if (ent.classname != "item_health_mega") return false; - - entity e = spawn(); - setthink(e, lms_extralife); - - e.nextthink = time + 0.1; - e.spawnflags = ent.spawnflags; - e.noalign = ent.noalign; - setorigin(e, ent.origin); - - return true; -} - -MUTATOR_HOOKFUNCTION(lms, ItemTouch) -{ - entity item = M_ARGV(0, entity); - entity toucher = M_ARGV(1, entity); - - if(item.itemdef == ITEM_ExtraLife) - { - Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES); - GameRules_scoring_add(toucher, LMS_LIVES, autocvar_g_lms_extra_lives); - return MUT_ITEMTOUCH_PICKUP; - } - - return MUT_ITEMTOUCH_CONTINUE; -} - -MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE) -{ - FOREACH_CLIENT(IS_REAL_CLIENT(it), { - ++M_ARGV(0, int); // activerealplayers - ++M_ARGV(1, int); // realplayers - }); - - return true; -} - -MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate) -{ - entity player = M_ARGV(0, entity); - - if(warmup_stage || player.lms_spectate_warning) - { - // for the forfeit message... - player.lms_spectate_warning = 2; - } - else - { - if(player.frags != FRAGS_SPECTATOR && player.frags != FRAGS_LMS_LOSER) - { - player.lms_spectate_warning = 1; - sprint(player, "WARNING: you won't be able to enter the game again after spectating in LMS. Use the same command again to spectate anyway.\n"); - } - return MUT_SPECCMD_RETURN; - } - return MUT_SPECCMD_CONTINUE; -} - -MUTATOR_HOOKFUNCTION(lms, CheckRules_World) -{ - M_ARGV(0, float) = WinningCondition_LMS(); - return true; -} - -MUTATOR_HOOKFUNCTION(lms, WantWeapon) -{ - M_ARGV(2, bool) = true; // all weapons -} - -MUTATOR_HOOKFUNCTION(lms, GetPlayerStatus) -{ - return true; -} - -MUTATOR_HOOKFUNCTION(lms, AddPlayerScore) -{ - if(game_stopped) - if(M_ARGV(0, entity) == SP_LMS_RANK) // score field - return true; // allow writing to this field in intermission as it is needed for newly joining players -} - -void lms_Initialize() -{ - lms_lowest_lives = 9999; -} diff --git a/qcsrc/server/mutators/mutator/gamemode_lms.qh b/qcsrc/server/mutators/mutator/gamemode_lms.qh deleted file mode 100644 index c69113a0c..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_lms.qh +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include "../gamemode.qh" - -.float lms_spectate_warning; -#define autocvar_g_lms_lives_override cvar("g_lms_lives_override") -void lms_Initialize(); - -REGISTER_MUTATOR(lms, false) -{ - MUTATOR_STATIC(); - MUTATOR_ONADD - { - GameRules_limit_score(((!autocvar_g_lms_lives_override) ? -1 : autocvar_g_lms_lives_override)); - GameRules_limit_lead(0); - GameRules_score_enabled(false); - GameRules_scoring(0, 0, 0, { - field(SP_LMS_LIVES, "lives", SFL_SORT_PRIO_SECONDARY); - field(SP_LMS_RANK, "rank", SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY | SFL_ALLOW_HIDE); - }); - - lms_Initialize(); - } - return 0; -} - -// lives related defs -float lms_lowest_lives; -float LMS_NewPlayerLives(); diff --git a/qcsrc/server/mutators/mutator/gamemode_race.qc b/qcsrc/server/mutators/mutator/gamemode_race.qc deleted file mode 100644 index e4109b72a..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_race.qc +++ /dev/null @@ -1,487 +0,0 @@ -#include "gamemode_race.qh" - -#include - -#define autocvar_g_race_laps_limit cvar("g_race_laps_limit") -float autocvar_g_race_qualifying_timelimit; -float autocvar_g_race_qualifying_timelimit_override; -int autocvar_g_race_teams; - -// legacy bot roles -.float race_checkpoint; -void havocbot_role_race(entity this) -{ - if(IS_DEAD(this)) - return; - - if (navigation_goalrating_timeout(this)) - { - navigation_goalrating_start(this); - - bool raw_touch_check = true; - int cp = this.race_checkpoint; - - LABEL(search_racecheckpoints) - IL_EACH(g_racecheckpoints, true, - { - if(it.cnt == cp || cp == -1) - { - // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint - // e.g. checkpoint in front of Stormkeep's warpzone - // the same workaround is applied in CTS game mode - if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30)) - { - cp = race_NextCheckpoint(cp); - raw_touch_check = false; - goto search_racecheckpoints; - } - navigation_routerating(this, it, 1000000, 5000); - } - }); - - navigation_goalrating_end(this); - - navigation_goalrating_timeout_set(this); - } -} - -void race_ScoreRules() -{ - GameRules_score_enabled(false); - GameRules_scoring(race_teams, 0, 0, { - if (race_teams) { - field_team(ST_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY); - field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY); - field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME); - field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME); - } else if (g_race_qualifying) { - field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME); - } else { - field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY); - field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME); - field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME); - } - }); -} - -void race_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later -{ - if(autocvar_sv_eventlog) - GameLogEcho(strcat(":race:", mode, ":", ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : ""))); -} - -float WinningCondition_Race(float fraglimit) -{ - float wc; - float n, c; - - n = 0; - c = 0; - FOREACH_CLIENT(IS_PLAYER(it), { - ++n; - if(CS(it).race_completed) - ++c; - }); - if(n && (n == c)) - return WINNING_YES; - wc = WinningCondition_Scores(fraglimit, 0); - - // ALWAYS initiate overtime, unless EVERYONE has finished the race! - if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME) - // do NOT support equality when the laps are all raced! - return WINNING_STARTSUDDENDEATHOVERTIME; - else - return WINNING_NEVER; -} - -float WinningCondition_QualifyingThenRace(float limit) -{ - float wc; - wc = WinningCondition_Scores(limit, 0); - - // NEVER initiate overtime - if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME) - { - return WINNING_YES; - } - - return wc; -} - -MUTATOR_HOOKFUNCTION(rc, ClientKill) -{ - if(g_race_qualifying) - M_ARGV(1, float) = 0; // killtime -} - -MUTATOR_HOOKFUNCTION(rc, AbortSpeedrun) -{ - entity player = M_ARGV(0, entity); - - if(autocvar_g_allow_checkpoints) - race_PreparePlayer(player); // nice try -} - -MUTATOR_HOOKFUNCTION(rc, PlayerPhysics) -{ - entity player = M_ARGV(0, entity); - float dt = M_ARGV(1, float); - - player.race_movetime_frac += dt; - float f = floor(player.race_movetime_frac); - player.race_movetime_frac -= f; - player.race_movetime_count += f; - player.race_movetime = player.race_movetime_frac + player.race_movetime_count; - -#ifdef SVQC - if(IS_PLAYER(player)) - { - if (player.race_penalty) - if (time > player.race_penalty) - player.race_penalty = 0; - if(player.race_penalty) - { - player.velocity = '0 0 0'; - set_movetype(player, MOVETYPE_NONE); - player.disableclientprediction = 2; - } - } -#endif - - // force kbd movement for fairness - float wishspeed; - vector wishvel; - - // if record times matter - // ensure nothing EVIL is being done (i.e. div0_evade) - // this hinders joystick users though - // but it still gives SOME analog control - wishvel.x = fabs(CS(player).movement.x); - wishvel.y = fabs(CS(player).movement.y); - if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y) - { - wishvel.z = 0; - wishspeed = vlen(wishvel); - if(wishvel.x >= 2 * wishvel.y) - { - // pure X motion - if(CS(player).movement.x > 0) - CS(player).movement_x = wishspeed; - else - CS(player).movement_x = -wishspeed; - CS(player).movement_y = 0; - } - else if(wishvel.y >= 2 * wishvel.x) - { - // pure Y motion - CS(player).movement_x = 0; - if(CS(player).movement.y > 0) - CS(player).movement_y = wishspeed; - else - CS(player).movement_y = -wishspeed; - } - else - { - // diagonal - if(CS(player).movement.x > 0) - CS(player).movement_x = M_SQRT1_2 * wishspeed; - else - CS(player).movement_x = -M_SQRT1_2 * wishspeed; - if(CS(player).movement.y > 0) - CS(player).movement_y = M_SQRT1_2 * wishspeed; - else - CS(player).movement_y = -M_SQRT1_2 * wishspeed; - } - } -} - -MUTATOR_HOOKFUNCTION(rc, reset_map_global) -{ - float s; - - Score_NicePrint(NULL); - - race_ClearRecords(); - PlayerScore_Sort(race_place, 0, 1, 0); - - FOREACH_CLIENT(true, { - if(it.race_place) - { - s = GameRules_scoring_add(it, RACE_FASTEST, 0); - if(!s) - it.race_place = 0; - } - race_EventLog(ftos(it.race_place), it); - }); - - if(g_race_qualifying == 2) - { - g_race_qualifying = 0; - independent_players = 0; - cvar_set("fraglimit", ftos(race_fraglimit)); - cvar_set("leadlimit", ftos(race_leadlimit)); - cvar_set("timelimit", ftos(race_timelimit)); - race_ScoreRules(); - } -} - -MUTATOR_HOOKFUNCTION(rc, ClientConnect) -{ - entity player = M_ARGV(0, entity); - - race_PreparePlayer(player); - player.race_checkpoint = -1; - - string rr = RACE_RECORD; - - if(IS_REAL_CLIENT(player)) - { - msg_entity = player; - race_send_recordtime(MSG_ONE); - race_send_speedaward(MSG_ONE); - - speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"))); - speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"))); - race_send_speedaward_alltimebest(MSG_ONE); - - float i; - for (i = 1; i <= RANKINGS_CNT; ++i) - { - race_SendRankings(i, 0, 0, MSG_ONE); - } - } -} - -MUTATOR_HOOKFUNCTION(rc, MakePlayerObserver) -{ - entity player = M_ARGV(0, entity); - - if(g_race_qualifying) - if(GameRules_scoring_add(player, RACE_FASTEST, 0)) - player.frags = FRAGS_LMS_LOSER; - else - player.frags = FRAGS_SPECTATOR; - - race_PreparePlayer(player); - player.race_checkpoint = -1; -} - -MUTATOR_HOOKFUNCTION(rc, PlayerSpawn) -{ - entity player = M_ARGV(0, entity); - entity spawn_spot = M_ARGV(1, entity); - - if(spawn_spot.target == "") - // Emergency: this wasn't a real spawnpoint. Can this ever happen? - race_PreparePlayer(player); - - // if we need to respawn, do it right - player.race_respawn_checkpoint = player.race_checkpoint; - player.race_respawn_spotref = spawn_spot; - - player.race_place = 0; -} - -MUTATOR_HOOKFUNCTION(rc, PutClientInServer) -{ - entity player = M_ARGV(0, entity); - - if(IS_PLAYER(player)) - if(!game_stopped) - { - if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn - race_PreparePlayer(player); - else // respawn - race_RetractPlayer(player); - - race_AbandonRaceCheck(player); - } -} - -MUTATOR_HOOKFUNCTION(rc, PlayerDies) -{ - entity frag_target = M_ARGV(2, entity); - - frag_target.respawn_flags |= RESPAWN_FORCE; - race_AbandonRaceCheck(frag_target); -} - -MUTATOR_HOOKFUNCTION(rc, HavocBot_ChooseRole) -{ - entity bot = M_ARGV(0, entity); - - bot.havocbot_role = havocbot_role_race; - return true; -} - -MUTATOR_HOOKFUNCTION(rc, GetPressedKeys) -{ - entity player = M_ARGV(0, entity); - - if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1) - { - if (!player.stored_netname) - player.stored_netname = strzone(uid2name(player.crypto_idfp)); - if(player.stored_netname != player.netname) - { - db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname); - strcpy(player.stored_netname, player.netname); - } - } - - if (!IS_OBSERVER(player)) - { - if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed)) - { - speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1'); - speedaward_holder = player.netname; - speedaward_uid = player.crypto_idfp; - speedaward_lastupdate = time; - } - if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1) - { - string rr = RACE_RECORD; - race_send_speedaward(MSG_ALL); - speedaward_lastsent = speedaward_speed; - if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "") - { - speedaward_alltimebest = speedaward_speed; - speedaward_alltimebest_holder = speedaward_holder; - speedaward_alltimebest_uid = speedaward_uid; - db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest)); - db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid); - race_send_speedaward_alltimebest(MSG_ALL); - } - } - } -} - -MUTATOR_HOOKFUNCTION(rc, ForbidPlayerScore_Clear) -{ - if(g_race_qualifying) - return true; // in qualifying, you don't lose score by observing -} - -MUTATOR_HOOKFUNCTION(rc, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE) -{ - M_ARGV(0, float) = race_teams; -} - -MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining) -{ - // announce remaining frags if not in qualifying mode - if(!g_race_qualifying) - return true; -} - -MUTATOR_HOOKFUNCTION(rc, GetRecords) -{ - int record_page = M_ARGV(0, int); - string ret_string = M_ARGV(1, string); - - for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i) - { - if(MapInfo_Get_ByID(i)) - { - float r = race_readTime(MapInfo_Map_bspname, 1); - - if(!r) - continue; - - string h = race_readName(MapInfo_Map_bspname, 1); - ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n"); - } - } - - M_ARGV(1, string) = ret_string; -} - -MUTATOR_HOOKFUNCTION(rc, HideTeamNagger) -{ - return true; // doesn't work so well -} - -MUTATOR_HOOKFUNCTION(rc, FixClientCvars) -{ - entity player = M_ARGV(0, entity); - - stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n"); -} - -MUTATOR_HOOKFUNCTION(rc, CheckRules_World) -{ - float checkrules_timelimit = M_ARGV(1, float); - float checkrules_fraglimit = M_ARGV(2, float); - - if(checkrules_timelimit >= 0) - { - if(!g_race_qualifying) - { - M_ARGV(0, float) = WinningCondition_Race(checkrules_fraglimit); - return true; - } - else if(g_race_qualifying == 2) - { - M_ARGV(0, float) = WinningCondition_QualifyingThenRace(checkrules_fraglimit); - return true; - } - } -} - -MUTATOR_HOOKFUNCTION(rc, ReadLevelCvars) -{ - if(g_race_qualifying == 2) - warmup_stage = 0; -} - -void race_Initialize() -{ - race_ScoreRules(); - if(g_race_qualifying == 2) - warmup_stage = 0; -} - -void rc_SetLimits() -{ - int fraglimit_override, leadlimit_override; - float timelimit_override, qualifying_override; - - if(autocvar_g_race_teams) - { - GameRules_teams(true); - race_teams = BITS(bound(2, autocvar_g_race_teams, 4)); - } - else - race_teams = 0; - - qualifying_override = autocvar_g_race_qualifying_timelimit_override; - fraglimit_override = autocvar_g_race_laps_limit; - leadlimit_override = 0; // currently not supported by race - timelimit_override = autocvar_timelimit_override; - - float want_qualifying = ((qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit) > 0; - - if(autocvar_g_campaign) - { - g_race_qualifying = 1; - independent_players = 1; - } - else if(want_qualifying) - { - g_race_qualifying = 2; - independent_players = 1; - race_fraglimit = (fraglimit_override >= 0) ? fraglimit_override : autocvar_fraglimit; - race_leadlimit = (leadlimit_override >= 0) ? leadlimit_override : autocvar_leadlimit; - race_timelimit = (timelimit_override >= 0) ? timelimit_override : autocvar_timelimit; - qualifying_override = (qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit; - fraglimit_override = 0; - leadlimit_override = 0; - timelimit_override = qualifying_override; - } - else - g_race_qualifying = 0; - GameRules_limit_score(fraglimit_override); - GameRules_limit_lead(leadlimit_override); - GameRules_limit_time(timelimit_override); - GameRules_limit_time_qualifying(qualifying_override); -} diff --git a/qcsrc/server/mutators/mutator/gamemode_race.qh b/qcsrc/server/mutators/mutator/gamemode_race.qh deleted file mode 100644 index 1e475e3ce..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_race.qh +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "../gamemode.qh" - -void rc_SetLimits(); -void race_Initialize(); - -REGISTER_MUTATOR(rc, false) -{ - MUTATOR_STATIC(); - MUTATOR_ONADD - { - rc_SetLimits(); - - race_Initialize(); - } - return 0; -} diff --git a/qcsrc/server/mutators/mutator/gamemode_tdm.qc b/qcsrc/server/mutators/mutator/gamemode_tdm.qc deleted file mode 100644 index aad319328..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_tdm.qc +++ /dev/null @@ -1,63 +0,0 @@ -#include "gamemode_tdm.qh" - -int autocvar_g_tdm_teams; -int autocvar_g_tdm_teams_override; - -/*QUAKED spawnfunc_tdm_team (0 .5 .8) (-16 -16 -24) (16 16 32) -Team declaration for TDM gameplay, this allows you to decide what team names and control point models are used in your map. -Note: If you use spawnfunc_tdm_team entities you must define at least 2! However, unlike domination, you don't need to make a blank one too. -Keys: -"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)... -"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */ -spawnfunc(tdm_team) -{ - if(!g_tdm || !this.cnt) { delete(this); return; } - - this.classname = "tdm_team"; - this.team = this.cnt + 1; -} - -// code from here on is just to support maps that don't have team entities -void tdm_SpawnTeam (string teamname, int teamcolor) -{ - entity this = new_pure(tdm_team); - this.netname = teamname; - this.cnt = teamcolor - 1; - this.team = teamcolor; - this.spawnfunc_checked = true; - //spawnfunc_tdm_team(this); -} - -void tdm_DelayedInit(entity this) -{ - // if no teams are found, spawn defaults - if(find(NULL, classname, "tdm_team") == NULL) - { - LOG_TRACE("No \"tdm_team\" entities found on this map, creating them anyway."); - - int numteams = autocvar_g_tdm_teams_override; - if(numteams < 2) { numteams = autocvar_g_tdm_teams; } - - int teams = BITS(bound(2, numteams, 4)); - if(teams & BIT(0)) - tdm_SpawnTeam("Red", NUM_TEAM_1); - if(teams & BIT(1)) - tdm_SpawnTeam("Blue", NUM_TEAM_2); - if(teams & BIT(2)) - tdm_SpawnTeam("Yellow", NUM_TEAM_3); - if(teams & BIT(3)) - tdm_SpawnTeam("Pink", NUM_TEAM_4); - } -} - -MUTATOR_HOOKFUNCTION(tdm, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE) -{ - M_ARGV(1, string) = "tdm_team"; - return true; -} - -MUTATOR_HOOKFUNCTION(tdm, Scores_CountFragsRemaining) -{ - // announce remaining frags - return true; -} diff --git a/qcsrc/server/mutators/mutator/gamemode_tdm.qh b/qcsrc/server/mutators/mutator/gamemode_tdm.qh deleted file mode 100644 index c163962fa..000000000 --- a/qcsrc/server/mutators/mutator/gamemode_tdm.qh +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include "../gamemode.qh" - -int autocvar_g_tdm_point_limit; -int autocvar_g_tdm_point_leadlimit; -bool autocvar_g_tdm_team_spawns; -void tdm_DelayedInit(entity this); - -REGISTER_MUTATOR(tdm, false) -{ - MUTATOR_STATIC(); - MUTATOR_ONADD - { - GameRules_teams(true); - GameRules_spawning_teams(autocvar_g_tdm_team_spawns); - GameRules_limit_score(autocvar_g_tdm_point_limit); - GameRules_limit_lead(autocvar_g_tdm_point_leadlimit); - - InitializeEntity(NULL, tdm_DelayedInit, INITPRIO_GAMETYPE); - } - return 0; -} diff --git a/qcsrc/server/player.qc b/qcsrc/server/player.qc index 43878e0fb..4d59943b6 100644 --- a/qcsrc/server/player.qc +++ b/qcsrc/server/player.qc @@ -5,7 +5,6 @@ #include "cheats.qh" #include "g_damage.qh" #include "handicap.qh" -#include "g_subs.qh" #include "miscfunctions.qh" #include "portals.qh" #include "teamplay.qh" @@ -15,8 +14,9 @@ #include "../common/anim.qh" #include "../common/animdecide.qh" #include "../common/csqcmodel_settings.qh" +#include "../common/gamemodes/sv_rules.qh" #include "../common/deathtypes/all.qh" -#include "../common/triggers/subs.qh" +#include "../common/mapobjects/subs.qh" #include "../common/playerstats.qh" #include "../lib/csqcmodel/sv_model.qh" @@ -25,7 +25,7 @@ #include "../common/physics/player.qh" #include "../common/effects/qc/_mod.qh" #include "../common/mutators/mutator/waypoints/waypointsprites.qh" -#include "../common/triggers/include.qh" +#include "../common/mapobjects/_mod.qh" #include "../common/wepent.qh" #include "weapons/weaponstats.qh" diff --git a/qcsrc/server/portals.qc b/qcsrc/server/portals.qc index ca0dc20fd..758c69bcc 100644 --- a/qcsrc/server/portals.qc +++ b/qcsrc/server/portals.qc @@ -6,8 +6,8 @@ #include "../common/constants.qh" #include "../common/deathtypes/all.qh" #include "../common/notifications/all.qh" -#include "../common/triggers/teleporters.qh" -#include "../common/triggers/subs.qh" +#include "../common/mapobjects/teleporters.qh" +#include "../common/mapobjects/subs.qh" #include "../common/util.qh" #include #include "../lib/csqcmodel/sv_model.qh" diff --git a/qcsrc/server/race.qc b/qcsrc/server/race.qc index 743d02d03..183da23f9 100644 --- a/qcsrc/server/race.qc +++ b/qcsrc/server/race.qc @@ -14,7 +14,8 @@ #include #include #include -#include "../common/triggers/subs.qh" +#include "../common/mapobjects/subs.qh" +#include #include "../lib/warpzone/util_server.qh" #include "../lib/warpzone/common.qh" #include "../common/mutators/mutator/waypoints/waypointsprites.qh" @@ -224,6 +225,14 @@ void race_send_speedaward_alltimebest(float msg) WriteString(msg, speedaward_alltimebest_holder); } +void race_send_rankings_cnt(float msg) +{ + WriteHeader(msg, TE_CSQC_RACE); + WriteByte(msg, RACE_NET_RANKINGS_CNT); + int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt); + WriteByte(msg, m); +} + void race_SendRankings(float pos, float prevpos, float del, float msg) { WriteHeader(msg, TE_CSQC_RACE); diff --git a/qcsrc/server/race.qh b/qcsrc/server/race.qh index 472827efa..32edca781 100644 --- a/qcsrc/server/race.qh +++ b/qcsrc/server/race.qh @@ -5,6 +5,8 @@ float race_teams; // scores const float ST_RACE_LAPS = 1; +int autocvar_g_cts_send_rankings_cnt = 15; + bool g_race_qualifying; float speedaward_lastsent; @@ -61,6 +63,8 @@ void race_send_speedaward(float msg); void race_send_speedaward_alltimebest(float msg); +void race_send_rankings_cnt(float msg); + void race_SendRankings(float pos, float prevpos, float del, float msg); void race_RetractPlayer(entity this); diff --git a/qcsrc/server/scores.qc b/qcsrc/server/scores.qc index 67c115c8a..2cb40a834 100644 --- a/qcsrc/server/scores.qc +++ b/qcsrc/server/scores.qc @@ -1,11 +1,18 @@ #include "scores.qh" #include "command/common.qh" -#include "mutators/_mod.qh" +#include "defs.qh" +#include +#include +#include #include #include "../common/playerstats.qh" #include "../common/teams.qh" +#include +#include #include +#include +#include .entity scorekeeper; entity teamscorekeepers[16]; diff --git a/qcsrc/server/spawnpoints.qc b/qcsrc/server/spawnpoints.qc index e48840883..dcf6016c5 100644 --- a/qcsrc/server/spawnpoints.qc +++ b/qcsrc/server/spawnpoints.qc @@ -1,16 +1,18 @@ #include "spawnpoints.qh" -#include "mutators/_mod.qh" +#include #include "g_world.qh" #include "race.qh" +#include "defs.qh" #include "../common/constants.qh" #include #include "../common/teams.qh" -#include "../common/triggers/subs.qh" -#include "../common/triggers/target/spawnpoint.qh" +#include "../common/mapobjects/subs.qh" +#include "../common/mapobjects/target/spawnpoint.qh" #include "../common/util.qh" #include "../lib/warpzone/common.qh" #include "../lib/warpzone/util_server.qh" +#include bool SpawnPoint_Send(entity this, entity to, int sf) { diff --git a/qcsrc/server/steerlib.qc b/qcsrc/server/steerlib.qc index 3418a2c4e..0a4dd6095 100644 --- a/qcsrc/server/steerlib.qc +++ b/qcsrc/server/steerlib.qc @@ -447,176 +447,3 @@ vector steerlib_beamsteer(entity this, vector dir, float length, float step, flo return normalize(vr + vl); } - - -////////////////////////////////////////////// -// Testting // -// Everything below this point is a mess :D // -////////////////////////////////////////////// -//#define TLIBS_TETSLIBS -#ifdef TLIBS_TETSLIBS -void flocker_die(entity this) -{ - Send_Effect(EFFECT_ROCKET_EXPLODE, this.origin, '0 0 0', 1); - - this.owner.cnt += 1; - this.owner = NULL; - - this.nextthink = time; - setthink(this, SUB_Remove); -} - - -void flocker_think(entity this) -{ - vector dodgemove,swarmmove; - vector reprellmove,wandermove,newmove; - - this.angles_x = this.angles.x * -1; - makevectors(this.angles); - this.angles_x = this.angles.x * -1; - - dodgemove = steerlib_traceavoid(this, 0.35,1000); - swarmmove = steerlib_flock(this, 500,75,700,500); - reprellmove = steerlib_repell(this, this.owner.enemy.origin+this.enemy.velocity,2000) * 700; - - if(dodgemove == '0 0 0') - { - this.pos1 = steerlib_wander(this, 0.5,0.1,this.pos1); - wandermove = this.pos1 * 50; - } - else - this.pos1 = normalize(this.velocity); - - dodgemove = dodgemove * vlen(this.velocity) * 5; - - newmove = swarmmove + reprellmove + wandermove + dodgemove; - this.velocity = movelib_inertmove_byspeed(this, newmove,300,0.2,0.9); - //this.velocity = movelib_inertmove(this, dodgemove,0.65); - - this.velocity = movelib_dragvec(this, 0.01,0.6); - - this.angles = vectoangles(this.velocity); - - if(this.health <= 0) - flocker_die(this); - else - this.nextthink = time + 0.1; -} - -MODEL(FLOCKER, "models/turrets/rocket.md3"); - -void spawn_flocker(entity this) -{ - entity flocker = new(flocker); - - setorigin(flocker, this.origin + '0 0 32'); - setmodel (flocker, MDL_FLOCKER); - setsize (flocker, '-3 -3 -3', '3 3 3'); - - flocker.flock_id = this.flock_id; - flocker.owner = this; - setthink(flocker, flocker_think); - flocker.nextthink = time + random() * 5; - PROJECTILE_MAKETRIGGER(flocker); - set_movetype(flocker, MOVETYPE_BOUNCEMISSILE); - flocker.effects = EF_LOWPRECISION; - flocker.velocity = randomvec() * 300; - flocker.angles = vectoangles(flocker.velocity); - flocker.health = 10; - flocker.pos1 = normalize(flocker.velocity + randomvec() * 0.1); - - IL_PUSH(g_flockers, flocker); - - this.cnt = this.cnt -1; - -} - -void flockerspawn_think(entity this) -{ - if(this.cnt > 0) - spawn_flocker(this); - - this.nextthink = time + this.delay; - -} - -void flocker_hunter_think(entity this) -{ - vector dodgemove,attractmove,newmove; - entity ee; - - this.angles_x = this.angles.x * -1; - makevectors(this.angles); - this.angles_x = this.angles.x * -1; - - if(this.enemy) - if(vdist(this.enemy.origin - this.origin, <, 64)) - { - ee = this.enemy; - ee.health = -1; - this.enemy = NULL; - - } - - if(!this.enemy) - { - IL_EACH(g_flockers, it.flock_id == this.flock_id, - { - if(it == this.owner || it == ee) - continue; - - if(!this.enemy || vlen2(this.origin - it.origin) > vlen2(this.origin - this.enemy.origin)) - this.enemy = it; - }); - } - - if(this.enemy) - attractmove = steerlib_attract(this, this.enemy.origin+this.enemy.velocity * 0.1,5000) * 1250; - else - attractmove = normalize(this.velocity) * 200; - - dodgemove = steerlib_traceavoid(this, 0.35,1500) * vlen(this.velocity); - - newmove = dodgemove + attractmove; - this.velocity = movelib_inertmove_byspeed(this, newmove,1250,0.3,0.7); - this.velocity = movelib_dragvec(this, 0.01,0.5); - - this.angles = vectoangles(this.velocity); - this.nextthink = time + 0.1; -} - - -float globflockcnt; -spawnfunc(flockerspawn) -{ - ++globflockcnt; - - if(!this.cnt) this.cnt = 20; - if(!this.delay) this.delay = 0.25; - if(!this.flock_id) this.flock_id = globflockcnt; - - setthink(this, flockerspawn_think); - this.nextthink = time + 0.25; - - this.enemy = new(FLock Hunter); - - setmodel(this.enemy, MDL_FLOCKER); - setorigin(this.enemy, this.origin + '0 0 768' + (randomvec() * 128)); - - this.enemy.scale = 3; - this.enemy.effects = EF_LOWPRECISION; - set_movetype(this.enemy, MOVETYPE_BOUNCEMISSILE); - PROJECTILE_MAKETRIGGER(this.enemy); - setthink(this.enemy, flocker_hunter_think); - this.enemy.nextthink = time + 10; - this.enemy.flock_id = this.flock_id; - this.enemy.owner = this; - - IL_PUSH(g_flockers, this); - IL_PUSH(g_flockers, this.enemy); -} -#endif - - - diff --git a/qcsrc/server/steerlib.qh b/qcsrc/server/steerlib.qh index 89ffb698e..4beb69f63 100644 --- a/qcsrc/server/steerlib.qh +++ b/qcsrc/server/steerlib.qh @@ -5,6 +5,3 @@ vector steerlib_arrive(entity this, vector point, float maximal_distance); vector steerlib_attract2(entity this, vector point, float min_influense, float max_distance, float max_influense); //vector steerlib_pull(entity this, vector point); - -IntrusiveList g_flockers; -STATIC_INIT(g_flockers) { g_flockers = IL_NEW(); } diff --git a/qcsrc/server/sv_main.qc b/qcsrc/server/sv_main.qc index 487ab1fc5..326c8d960 100644 --- a/qcsrc/server/sv_main.qc +++ b/qcsrc/server/sv_main.qc @@ -2,13 +2,14 @@ #include "anticheat.qh" #include "g_hook.qh" +#include "g_damage.qh" #include "g_world.qh" #include "bot/api.qh" #include "command/common.qh" -#include "mutators/_mod.qh" +#include #include "weapons/csqcprojectile.qh" #include "../common/constants.qh" @@ -18,6 +19,7 @@ #include "../common/util.qh" #include "../common/vehicles/all.qh" +#include #include #include "../lib/csqcmodel/sv_model.qh" @@ -383,3 +385,15 @@ void WarpZone_PostInitialize_Callback() } delete(tracetest_ent); } + +/* +================== +main + +unused but required by the engine +================== +*/ +void main () +{ + +} diff --git a/qcsrc/server/sv_main.qh b/qcsrc/server/sv_main.qh index 7f86d19c0..93480cf28 100644 --- a/qcsrc/server/sv_main.qh +++ b/qcsrc/server/sv_main.qh @@ -1,3 +1,12 @@ #pragma once bool expr_evaluate(string s); + +/* +================== +main + +unused but required by the engine +================== +*/ +void main (); diff --git a/qcsrc/server/teamplay.qc b/qcsrc/server/teamplay.qc index 43aa4a26b..d9bab5d7f 100644 --- a/qcsrc/server/teamplay.qc +++ b/qcsrc/server/teamplay.qc @@ -9,10 +9,10 @@ #include "command/vote.qh" -#include "mutators/_mod.qh" +#include #include "../common/deathtypes/all.qh" -#include "../common/gamemodes/_mod.qh" +#include #include "../common/teams.qh" void TeamchangeFrags(entity e) @@ -105,7 +105,7 @@ string getwelcomemessage(entity this) if(g_weaponarena) { if(g_weaponarena_random) - modifications = strcat(modifications, ", ", ftos(g_weaponarena_random), " of ", g_weaponarena_list, " Arena"); + modifications = strcat(modifications, ", ", ftos(g_weaponarena_random), " of ", g_weaponarena_list, " Arena"); // TODO: somehow get this into the mutator else modifications = strcat(modifications, ", ", g_weaponarena_list, " Arena"); } diff --git a/qcsrc/server/weapons/accuracy.qc b/qcsrc/server/weapons/accuracy.qc index 1eda4e25b..2d553ba16 100644 --- a/qcsrc/server/weapons/accuracy.qc +++ b/qcsrc/server/weapons/accuracy.qc @@ -1,6 +1,6 @@ #include "accuracy.qh" -#include "../mutators/_mod.qh" +#include #include #include #include diff --git a/qcsrc/server/weapons/hitplot.qc b/qcsrc/server/weapons/hitplot.qc index e6bdd00d2..e79e9ddb6 100644 --- a/qcsrc/server/weapons/hitplot.qc +++ b/qcsrc/server/weapons/hitplot.qc @@ -3,7 +3,6 @@ #include #include #include "../antilag.qh" -#include "../g_subs.qh" #include #include #include diff --git a/qcsrc/server/weapons/spawning.qc b/qcsrc/server/weapons/spawning.qc index 17f2ddeee..204b5a763 100644 --- a/qcsrc/server/weapons/spawning.qc +++ b/qcsrc/server/weapons/spawning.qc @@ -2,7 +2,7 @@ #include "weaponsystem.qh" #include "../resources.qh" -#include "../mutators/_mod.qh" +#include #include #include #include diff --git a/qcsrc/server/weapons/throwing.qc b/qcsrc/server/weapons/throwing.qc index 821a02327..462af14d8 100644 --- a/qcsrc/server/weapons/throwing.qc +++ b/qcsrc/server/weapons/throwing.qc @@ -3,13 +3,13 @@ #include "weaponsystem.qh" #include "../resources.qh" #include "../items.qh" -#include "../mutators/_mod.qh" +#include #include #include "../g_damage.qh" #include #include #include -#include +#include #include #include #include @@ -152,19 +152,7 @@ bool W_IsWeaponThrowable(entity this, int w) if(w == WEP_Null.m_id) return false; - #if 0 - if(start_weapons & WepSet_FromWeapon(Weapons_from(w))) - { - // start weapons that take no ammo can't be dropped (this prevents dropping the laser, as long as it continues to use no ammo) - if(start_items & IT_UNLIMITED_WEAPON_AMMO) - return false; - if((Weapons_from(w)).ammo_type == RESOURCE_NONE) - return false; - } - return true; - #else return (Weapons_from(w)).weaponthrowable; - #endif } // toss current weapon diff --git a/qcsrc/server/weapons/tracing.qc b/qcsrc/server/weapons/tracing.qc index a3898c627..ddf1ff262 100644 --- a/qcsrc/server/weapons/tracing.qc +++ b/qcsrc/server/weapons/tracing.qc @@ -8,7 +8,6 @@ #include "weaponsystem.qh" #include "../g_damage.qh" -#include "../g_subs.qh" #include "../antilag.qh" #include @@ -81,7 +80,10 @@ void W_SetupShot_Dir_ProjectileSize_Range(entity ent, .entity weaponentity, vect tracebox(w_shotorg, mi, ma, w_shotorg + v_forward * (vecs.x + nudge), MOVE_NORMAL, ent); w_shotorg = trace_endpos - v_forward * nudge; // calculate the shotdir from the chosen shotorg - w_shotdir = normalize(w_shotend - w_shotorg); + if(W_DualWielding(ent)) + w_shotdir = s_forward; + else + w_shotdir = normalize(w_shotend - w_shotorg); //vector prevdir = w_shotdir; //vector prevorg = w_shotorg; diff --git a/qcsrc/server/weapons/weaponsystem.qc b/qcsrc/server/weapons/weaponsystem.qc index 011d5cbc1..9c62ec22e 100644 --- a/qcsrc/server/weapons/weaponsystem.qc +++ b/qcsrc/server/weapons/weaponsystem.qc @@ -3,9 +3,9 @@ #include "selection.qh" #include "../command/common.qh" -#include "../mutators/_mod.qh" +#include #include "../round_handler.qh" -#include "../resources.qh" +#include #include #include #include diff --git a/randomitems-overkill.cfg b/randomitems-overkill.cfg new file mode 100644 index 000000000..e2a2617e5 --- /dev/null +++ b/randomitems-overkill.cfg @@ -0,0 +1,21 @@ +// Random items mutator config for Overkill ruleset + +// Map items + +set g_random_items_item_health_mega_probability 1 "Probability of random mega health spawning in the map during overkill." +set g_random_items_item_armor_small_probability 10 "Probability of random small armor spawning in the map during overkill." +set g_random_items_item_armor_medium_probability 4 "Probability of random medium armor spawning in the map during overkill." +set g_random_items_item_armor_big_probability 2 "Probability of random big armor spawning in the map during overkill." +set g_random_items_item_armor_mega_probability 1 "Probability of random mega armor spawning in the map during overkill." +set g_random_items_weapon_okhmg_probability 0.5 "Probability of random overkill HMG spawning in the map during overkill." +set g_random_items_weapon_okrpc_probability 0.5 "Probability of random overkill RPC spawning in the map during overkill." + +// Loot + +set g_random_loot_item_health_mega_probability 1 "Probability of random mega health spawning as loot during overkill." +set g_random_loot_item_armor_small_probability 10 "Probability of random small armor spawning as loot during overkill." +set g_random_loot_item_armor_medium_probability 4 "Probability of random medium armor spawning as loot during overkill." +set g_random_loot_item_armor_big_probability 2 "Probability of random big armor spawning as loot during overkill." +set g_random_loot_item_armor_mega_probability 1 "Probability of random mega armor spawning as loot during overkill." +set g_random_loot_weapon_okhmg_probability 1 "Probability of random overkill HMG spawning as loot during overkill." +set g_random_loot_weapon_okrpc_probability 1 "Probability of random overkill RPC spawning as loot during overkill." diff --git a/randomitems-xonotic.cfg b/randomitems-xonotic.cfg index 1491ffd0c..47611a9e2 100644 --- a/randomitems-xonotic.cfg +++ b/randomitems-xonotic.cfg @@ -91,6 +91,11 @@ set g_random_items_weapon_hlac_probability 0 "Probability of random HLAC spawnin set g_random_items_weapon_rifle_probability 0 "Probability of random rifle spawning in the map." set g_random_items_weapon_seeker_probability 0 "Probability of random TAG seeker spawning in the map." set g_random_items_weapon_vaporizer_probability 0 "Probability of random vaporizer spawning in the map." +set g_random_items_weapon_okshotgun_probability 0 "Probability of random overkill shotgun spawning in the map." +set g_random_items_weapon_okmachinegun_probability 0 "Probability of random overkill machinegun spawning in the map." +set g_random_items_weapon_oknex_probability 0 "Probability of random overkill nex spawning in the map." +set g_random_items_weapon_okhmg_probability 0 "Probability of random overkill HMG spawning in the map." +set g_random_items_weapon_okrpc_probability 0 "Probability of random overkill RPC spawning in the map." set g_random_items_item_strength_probability 1 "Probability of random strength spawning in the map." set g_random_items_item_shield_probability 1 "Probability of random shield spawning in the map." set g_random_items_item_fuel_regen_probability 0 "Probability of random fuel regeneration spawning in the map." @@ -99,13 +104,6 @@ set g_random_items_item_vaporizer_cells_probability 20 "Probability of random va set g_random_items_item_invisibility_probability 1 "Probability of random invisibility spawning in the map." set g_random_items_item_extralife_probability 1 "Probability of random extra life spawning in the map." set g_random_items_item_speed_probability 1 "Probability of random speed spawning in the map." -set g_random_items_overkill_item_health_mega_probability 1 "Probability of random mega health spawning in the map during overkill." -set g_random_items_overkill_item_armor_small_probability 10 "Probability of random small armor spawning in the map during overkill." -set g_random_items_overkill_item_armor_medium_probability 4 "Probability of random medium armor spawning in the map during overkill." -set g_random_items_overkill_item_armor_big_probability 2 "Probability of random big armor spawning in the map during overkill." -set g_random_items_overkill_item_armor_mega_probability 1 "Probability of random mega armor spawning in the map during overkill." -set g_random_items_overkill_weapon_okhmg_probability 0.5 "Probability of random overkill HMG spawning in the map during overkill." -set g_random_items_overkill_weapon_okrpc_probability 0.5 "Probability of random overkill RPC spawning in the map during overkill." // Loot @@ -155,6 +153,11 @@ set g_random_loot_weapon_hlac_probability 0 "Probability of random HLAC spawning set g_random_loot_weapon_rifle_probability 0 "Probability of random rifle spawning as loot." set g_random_loot_weapon_seeker_probability 0 "Probability of random TAG seeker spawning as loot." set g_random_loot_weapon_vaporizer_probability 0 "Probability of random vaporizer spawning as loot." +set g_random_loot_weapon_okshotgun_probability 0 "Probability of random overkill shotgun spawning as loot." +set g_random_loot_weapon_okmachinegun_probability 0 "Probability of random overkill machinegun spawning as loot." +set g_random_loot_weapon_oknex_probability 0 "Probability of random overkill nex spawning as loot." +set g_random_loot_weapon_okhmg_probability 0 "Probability of random overkill HMG spawning as loot." +set g_random_loot_weapon_okrpc_probability 0 "Probability of random overkill RPC spawning as loot." set g_random_loot_item_strength_probability 1 "Probability of random strength spawning as loot." set g_random_loot_item_shield_probability 1 "Probability of random shield spawning as loot." set g_random_loot_item_fuel_regen_probability 0 "Probability of random fuel regeneration spawning as loot." @@ -163,10 +166,3 @@ set g_random_loot_item_vaporizer_cells_probability 20 "Probability of random vap set g_random_loot_item_invisibility_probability 1 "Probability of random invisibility spawning as loot." set g_random_loot_item_extralife_probability 1 "Probability of random extra life spawning as loot." set g_random_loot_item_speed_probability 1 "Probability of random speed spawning as loot." -set g_random_loot_overkill_item_health_mega_probability 1 "Probability of random mega health spawning as loot during overkill." -set g_random_loot_overkill_item_armor_small_probability 10 "Probability of random small armor spawning as loot during overkill." -set g_random_loot_overkill_item_armor_medium_probability 4 "Probability of random medium armor spawning as loot during overkill." -set g_random_loot_overkill_item_armor_big_probability 2 "Probability of random big armor spawning as loot during overkill." -set g_random_loot_overkill_item_armor_mega_probability 1 "Probability of random mega armor spawning as loot during overkill." -set g_random_loot_overkill_weapon_okhmg_probability 1 "Probability of random overkill HMG spawning as loot during overkill." -set g_random_loot_overkill_weapon_okrpc_probability 1 "Probability of random overkill RPC spawning as loot during overkill." diff --git a/ruleset-overkill.cfg b/ruleset-overkill.cfg index 49eb730c9..cc512e0c4 100644 --- a/ruleset-overkill.cfg +++ b/ruleset-overkill.cfg @@ -5,6 +5,7 @@ exec xonotic-server.cfg exec balance-overkill.cfg exec physicsOverkill.cfg +exec randomitems-overkill.cfg // general gameplay set g_overkill 1