From: Samual Date: Mon, 15 Aug 2011 05:23:18 +0000 (-0400) Subject: Merge remote branch 'origin/master' into samual/mutator_ctf X-Git-Tag: xonotic-v0.7.0~240^2~171 X-Git-Url: https://git.xonotic.org/?a=commitdiff_plain;h=7ec398db61b56e3c74bf0dbef1f6740a81cc7ced;p=xonotic%2Fxonotic-data.pk3dir.git Merge remote branch 'origin/master' into samual/mutator_ctf Conflicts: qcsrc/server/cl_client.qc qcsrc/server/cl_player.qc --- 7ec398db61b56e3c74bf0dbef1f6740a81cc7ced diff --cc qcsrc/server/cl_client.qc index ed3027a19,a497f8c34..81f3b0944 --- a/qcsrc/server/cl_client.qc +++ b/qcsrc/server/cl_client.qc @@@ -622,8 -622,11 +622,11 @@@ void PutObserverInServer (void self.alivetime = 0; } + if(self.vehicle) + vehicles_exit(VHEF_RELESE); + if(self.flagcarried) - DropFlag(self.flagcarried, world, world); + ctf_Handle_Drop(self); // FIXCTF if(self.ballcarried && g_nexball) DropBall(self.ballcarried, self.origin + self.ballcarried.origin, self.velocity); @@@ -2615,6 -2596,25 +2596,21 @@@ void SpectatorThink( self.flags |= FL_CLIENT | FL_NOTARGET; } -float ctf_usekey(); + void PlayerUseKey() + { + if(self.classname != "player") + return; + + if(self.vehicle) + { + vehicles_exit(VHEF_NORMAL); + return; + } + + // a use key was pressed; call handlers - if(ctf_usekey()) - return; - + MUTATOR_CALLHOOK(PlayerUseKey); + } + .float touchexplode_time; /* @@@ -2624,7 -2624,8 +2620,8 @@@ PlayerPreThin Called every frame for each client before the physics are run ============= */ + .float usekeypressed; -void() ctf_setstatus; +//void() ctf_setstatus; void() nexball_setstatus; .float items_added; void PlayerPreThink (void) diff --cc qcsrc/server/cl_player.qc index 6cb150f9e,6f82d9f10..376e0ec8c --- a/qcsrc/server/cl_player.qc +++ b/qcsrc/server/cl_player.qc @@@ -630,13 -642,12 +642,13 @@@ void PlayerDamage (entity inflictor, en if(self.flagcarried) { + // FIXCTF - if(attacker.classname != "player" && attacker.classname != "gib") + if(attacker.classname != "player") - DropFlag(self.flagcarried, self, attacker); // penalty for flag loss by suicide + ctf_Handle_Drop(self); // penalty for flag loss by suicide else if(attacker.team == self.team) - DropFlag(self.flagcarried, attacker, attacker); // penalty for flag loss by suicide/teamkill + ctf_Handle_Drop(self); // penalty for flag loss by suicide/teamkill else - DropFlag(self.flagcarried, world, attacker); + ctf_Handle_Drop(self); } if(self.ballcarried && g_nexball) DropBall(self.ballcarried, self.origin, self.velocity); diff --cc qcsrc/server/mutators/gamemode_ctf.qc index 8a261ee08,000000000..e50431938 mode 100644,000000..100644 --- a/qcsrc/server/mutators/gamemode_ctf.qc +++ b/qcsrc/server/mutators/gamemode_ctf.qc @@@ -1,876 -1,0 +1,881 @@@ +// ================================================================ +// Official capture the flag game mode coding, reworked by Samual +// Last updated: March 28th, 2011 +// ================================================================ + +// Flag constants +#define FLAG_MIN (PL_MIN + '0 0 -13') +#define FLAG_MAX (PL_MAX + '0 0 -13') +#define FLAG_CARRY_POS '-15 0 7' + +.entity bot_basewaypoint; // flag waypointsprite +.entity wps_flagbase; +.entity wps_flagcarrier; +.entity wps_flagdropped; + +entity ctf_worldflaglist; // CTF flags in the map +.entity ctf_worldflagnext; + +.vector ctf_spawnorigin; // stored vector for where the flag is placed on the map itself. + +float ctf_captimerecord; // record time for capturing the flag +.float ctf_pickuptime; +.float ctf_pickupid; +.float ctf_dropperid; // don't allow spam of dropping the flag +.float ctf_droptime; +.float ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally) + + +.float next_take_time; // Delay between when the person can pick up a flag // is this obsolete from the stuff above? + +// CaptureShield: If the player is too bad to be allowed to capture, shield them from taking the flag. +.float 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 + +// after game mode is finished, these will be changed to use #define with other entities so as to not create many more. + +// ================== +// Misc CTF functions +// ================== + +float ctf_ReadScore(string parameter) // make this obsolete +{ + if(g_ctf_win_mode != 2) + return cvar(strcat("g_ctf_personal", parameter)); + else + return cvar(strcat("g_ctf_flag", parameter)); +} + +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, float flagteam, entity actor) // use an alias for easy changing and quick editing later +{ + if(autocvar_sv_eventlog) + GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : ""))); +} + + +// ======================= +// CaptureShield Functions +// ======================= + +float ctf_CaptureShield_CheckStatus(entity p) +{ + float s, se; + entity e; + float players_worseeq, players_total; + + if(ctf_captureshield_max_ratio <= 0) + return FALSE; + + s = PlayerScore_Add(p, SP_SCORE, 0); + if(s >= -ctf_captureshield_min_negscore) + return FALSE; + + players_total = players_worseeq = 0; + FOR_EACH_PLAYER(e) + { + if(e.team != p.team) + continue; + se = PlayerScore_Add(e, SP_SCORE, 0); + if(se <= s) + ++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, float wanted_status) +{ + float updated_status = ctf_CaptureShield_CheckStatus(player); + if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only + { + if(updated_status) // TODO csqc notifier for this // Samual: How? + centerprint_atprio(player, CENTERPRIO_SHIELDING, "^3You are now ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Make some defensive scores before trying again."); + else + centerprint_atprio(player, CENTERPRIO_SHIELDING, "^3You are now free.\n\n^3Feel free to ^1try to capture^3 the flag again\n^3if you think you will succeed."); + + player.ctf_captureshielded = updated_status; + } +} + +float ctf_CaptureShield_Customize() +{ + if not(other.ctf_captureshielded) + return FALSE; + if(self.team == other.team) + return FALSE; + return TRUE; +} + +void ctf_CaptureShield_Touch() +{ + if not(other.ctf_captureshielded) + return; + if(self.team == other.team) + return; + vector mymid; + vector othermid; + mymid = (self.absmin + self.absmax) * 0.5; + othermid = (other.absmin + other.absmax) * 0.5; + Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force); + centerprint_atprio(other, CENTERPRIO_SHIELDING, "^3You are ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Get some defensive scores before trying again."); +} + +void ctf_CaptureShield_Spawn(entity flag) +{ + entity e; + e = spawn(); + e.enemy = self; + e.team = self.team; + e.touch = ctf_CaptureShield_Touch; + e.customizeentityforclient = ctf_CaptureShield_Customize; + e.classname = "ctf_captureshield"; + e.effects = EF_ADDITIVE; + e.movetype = MOVETYPE_NOCLIP; + e.solid = SOLID_TRIGGER; + e.avelocity = '7 0 11'; + setorigin(e, self.origin); + setmodel(e, "models/ctf/shield.md3"); + e.scale = 0.5; + setsize(e, e.scale * e.mins, e.scale * e.maxs); +} + + +// ============== +// Event Handlers +// ============== + +void ctf_Handle_Drop(entity player) +{ + entity flag = player.flagcarried; + + if(!flag) { return; } + if(flag.speedrunning) { ctf_RespawnFlag(flag); return; } + + // reset the flag + setattachment(flag, world, ""); + setorigin(flag, player.origin - '0 0 24' + '0 0 37'); + flag.owner.flagcarried = world; + flag.owner = world; + flag.movetype = MOVETYPE_TOSS; + flag.solid = SOLID_TRIGGER; + flag.takedamage = DAMAGE_YES; + flag.velocity = ('0 0 200' + ('0 100 0' * crandom()) + ('100 0 0' * crandom())); + flag.pain_finished = time + autocvar_g_ctf_flag_returntime; // replace this later + + flag.ctf_droptime = time; + flag.ctf_dropperid = player.playerid; + flag.ctf_status = FLAG_DROPPED; + + // messages and sounds + Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO); - sound(flag, CHAN_TRIGGER, flag.noise4, VOL_BASE, ATTN_NONE); ++ sound(flag, CH_TRIGGER, flag.noise4, VOL_BASE, ATTN_NONE); + ctf_EventLog("dropped", player.team, player); + + // scoring + PlayerTeamScore_AddScore(player, -ctf_ReadScore("penalty_drop")); + PlayerScore_Add(player, SP_CTF_DROPS, 1); + + // waypoints - WaypointSprite_Spawn("flagdropped", 0, 0, flag, '0 0 64', world, player.team, flag, wps_flagdropped, FALSE); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team) ++ WaypointSprite_Spawn("flagdropped", 0, 0, flag, '0 0 64', world, player.team, flag, wps_flagdropped, FALSE, RADARICON_FLAG, '0 1 1'); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team) + WaypointSprite_Ping(player.wps_flagcarrier); + WaypointSprite_Kill(player.wps_flagcarrier); + + // captureshield + ctf_CaptureShield_Update(player, 0); // shield only + + // check if the flag will fall off the map + trace_startsolid = FALSE; + tracebox(flag.origin, flag.mins, flag.maxs, flag.origin, TRUE, flag); + if(trace_startsolid) + dprint("FLAG FALLTHROUGH will happen SOON\n"); +} + +void ctf_Handle_Capture(entity flag, entity player) +{ + // declarations + float cap_time, cap_record, success; + string cap_message, refername; + + // records + if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots)) { + cap_record = ctf_captimerecord; + cap_time = (time - player.flagcarried.ctf_pickuptime); + + refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname")); + refername = ((refername == player.netname) ? "their" : strcat(refername, "^7's")); + + if(!ctf_captimerecord) + { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds"); success = TRUE; } + else if(cap_time < cap_record) + { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, breaking ", refername, " previous record of ", ftos_decimals(cap_record, 2), " seconds"); success = TRUE; } + else + { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, failing to break ", refername, " record of ", ftos_decimals(cap_record, 2), " seconds"); success = FALSE; } + + if(success) { + 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, (time - cap_time), cap_time); } } + + // messages and sounds + Send_KillNotification(player.netname, player.flagcarried.netname, cap_message, INFO_CAPTUREFLAG, MSG_INFO); - sound(player, CHAN_AUTO, flag.noise2, VOL_BASE, ATTN_NONE); // "ctf/*_capture.wav" ++ sound(player, CH_TRIGGER, flag.noise2, VOL_BASE, ATTN_NONE); // "ctf/*_capture.wav" + ctf_EventLog("capture", player.flagcarried.team, player); + + // scoring + PlayerTeamScore_AddScore(player, ctf_ReadScore("score_capture")); + PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1); + + // effects + if (autocvar_g_ctf_flag_capture_effects) + { + pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1); + //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1); + } + + // waypointsprites + WaypointSprite_Kill(player.wps_flagcarrier); + + // reset the flag + if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); } + + ctf_RespawnFlag(player.flagcarried); +} + +void ctf_Handle_Return(entity flag, entity player) +{ + // messages and sounds + Send_KillNotification (player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO); - sound(player, CHAN_AUTO, flag.noise1, VOL_BASE, ATTN_NONE); ++ sound(player, CH_TRIGGER, flag.noise1, VOL_BASE, ATTN_NONE); + ctf_EventLog("return", flag.team, player); + + // scoring + PlayerTeamScore_AddScore(player, ctf_ReadScore(strcat("score_return", ((player.playerid == flag.playerid) ? "_by_killer" : "")))); // reward for return + PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns + + TeamScore_AddToTeam(((flag.team == COLOR_TEAM1) ? COLOR_TEAM2 : COLOR_TEAM1), ST_SCORE, -ctf_ReadScore("penalty_returned")); // punish the team who was last carrying it + FOR_EACH_PLAYER(player) if(player.playerid == flag.ctf_dropperid) // punish the player who dropped the flag + { + PlayerScore_Add(player, SP_SCORE, -ctf_ReadScore("penalty_returned")); + ctf_CaptureShield_Update(player, 0); // shield only + } + + // waypointsprites + WaypointSprite_Kill(flag.wps_flagdropped); + + // reset the flag + ctf_RespawnFlag(flag); +} + +void ctf_Handle_Pickup_Base(entity flag, entity player) +{ + entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players + string verbosename; // holds the name of the player OR no name at all for printing in the centerprints + + // attach the flag to the player + flag.owner = player; + player.flagcarried = flag; + setattachment(flag, player, ""); + setorigin(flag, FLAG_CARRY_POS); + + // set up the flag + flag.movetype = MOVETYPE_NONE; + flag.takedamage = DAMAGE_NO; + flag.solid = SOLID_NOT; + flag.angles = '0 0 0'; + flag.ctf_pickuptime = time; // used for timing runs + flag.ctf_pickupid = player.playerid; + flag.ctf_status = FLAG_CARRY; + + // messages and sounds + Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO); - sound(player, CHAN_AUTO, flag.noise, VOL_BASE, ATTN_NONE); ++ sound(player, CH_TRIGGER, flag.noise, VOL_BASE, ATTN_NONE); + ctf_EventLog("steal", flag.team, player); + verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat("(", player.netname, ")") : ""); // replace TRUE with an autocvar for it. + FOR_EACH_PLAYER(tmp_player) + if(tmp_player.team == flag.team) + centerprint(tmp_player, strcat("The enemy ", verbosename, "got your flag! Retrieve it!")); + else if((tmp_player.team == player.team) && (tmp_player != player)) + centerprint(tmp_player, strcat("Your team mate ", verbosename, "got the flag! Protect them!")); + + // scoring + PlayerTeamScore_AddScore(player, ctf_ReadScore("score_pickup_base")); + PlayerScore_Add(player, SP_CTF_PICKUPS, 1); + + // speedrunning + 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 + if (autocvar_g_ctf_flag_pickup_effects) + { + pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1); + } + + // waypoints - WaypointSprite_Spawn("flagcarrier", 0, 0, player, '0 0 64', world, player.team, player, wps_flagcarrier, FALSE); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team) ++ WaypointSprite_Spawn("flagcarrier", 0, 0, player, '0 0 64', world, player.team, player, wps_flagcarrier, FALSE, RADARICON_FLAG, '1 1 0'); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team) + WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2); + WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent)); + WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0'); + WaypointSprite_Ping(player.wps_flagcarrier); +} + +void ctf_Handle_Pickup_Dropped(entity flag, entity player) // make sure this works +{ + // declarations + float returnscore = bound(0, (flag.pain_finished - time) / autocvar_g_ctf_flag_returntime, 1); // can this be division by zero? + entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players + string verbosename; // holds the name of the player OR no name at all for printing in the centerprints + + // attach the flag to the player + flag.owner = player; + player.flagcarried = flag; + setattachment(flag, player, ""); + setorigin(flag, FLAG_CARRY_POS); + + // set up the flag + flag.movetype = MOVETYPE_NONE; + flag.takedamage = DAMAGE_NO; + flag.solid = SOLID_NOT; + flag.angles = '0 0 0'; + //flag.ctf_pickuptime = time; // don't update pickuptime since this isn't a real steal. + flag.ctf_pickupid = player.playerid; + flag.ctf_status = FLAG_CARRY; + + // messages and sounds + Send_KillNotification (player.netname, flag.netname, "", INFO_PICKUPFLAG, MSG_INFO); - sound (player, CHAN_AUTO, flag.noise, VOL_BASE, ATTN_NONE); ++ sound (player, CH_TRIGGER, flag.noise, VOL_BASE, ATTN_NONE); + ctf_EventLog("pickup", flag.team, player); + verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat("(", player.netname, ")") : ""); + FOR_EACH_PLAYER(tmp_player) + if(tmp_player.team == flag.team) + centerprint(tmp_player, strcat("The enemy ", verbosename, "got your flag! Retrieve it!")); + else if((tmp_player.team == player.team) && (tmp_player != player)) + centerprint(tmp_player, strcat("Your team mate ", verbosename, "got the flag! Protect them!")); + + // scoring + returnscore = floor((ctf_ReadScore("score_pickup_dropped_late") * (1-returnscore) + ctf_ReadScore("score_pickup_dropped_early") * returnscore) + 0.5); + print("score is ", ftos(returnscore), "\n"); + PlayerTeamScore_AddScore(player, returnscore); + PlayerScore_Add(player, SP_CTF_PICKUPS, 1); + + // effects + if (autocvar_g_ctf_flag_pickup_effects) // field pickup effect + { + pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1); + } + + // waypoints + WaypointSprite_Kill(flag.wps_flagdropped); - WaypointSprite_Spawn("flagcarrier", 0, 0, player, '0 0 64', world, player.team, player, wps_flagcarrier, FALSE); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team) ++ WaypointSprite_Spawn("flagcarrier", 0, 0, player, '0 0 64', world, player.team, player, wps_flagcarrier, FALSE, RADARICON_FLAG, '1 1 0'); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team) + WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2); + WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent)); + WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0'); + WaypointSprite_Ping(player.wps_flagcarrier); +} + + +// =================== +// Main Flag Functions +// =================== + +void ctf_FlagThink() +{ + // declarations + entity tmp_entity; + + self.nextthink = time + 0.1; // only 10 fps, more is unnecessary. + + // captureshield + if(self == ctf_worldflaglist) // only for the first flag + FOR_EACH_CLIENT(tmp_entity) + ctf_CaptureShield_Update(tmp_entity, 1); // release shield only + + // sanity checks + if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished + dprint("wtf the flag got squished?\n"); + tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self); + if(!trace_startsolid) // can we resize it without getting stuck? + setsize(self, FLAG_MIN, FLAG_MAX); } + + if(self.owner.classname != "player" || (self.owner.deadflag) || (self.owner.flagcarried != self)) { + dprint("CANNOT HAPPEN - player dead and STILL had a flag!\n"); + ctf_Handle_Drop(self.owner); + return; } + + // main think method + switch(self.ctf_status) + { + case FLAG_BASE: // nothing to do here + return; + + case FLAG_DROPPED: + // flag fallthrough? FIXME remove this if bug is really fixed now + if(self.origin_z < -131072) + { + dprint("FLAG FALLTHROUGH just happened\n"); + self.pain_finished = 0; + } + setattachment(self, world, ""); + if(time > self.pain_finished) + { + bprint("The ", self.netname, " has returned to base\n"); - sound (self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE); ++ sound (self, CH_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE); + ctf_EventLog("returned", self.team, world); + ctf_RespawnFlag(self); + } + return; + + case FLAG_CARRY: + if((self.owner) && (self.speedrunning) && (ctf_captimerecord) && (time >= self.ctf_pickuptime + ctf_captimerecord)) + { + bprint("The ", self.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n"); - sound (self, CHAN_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE); ++ sound (self, CH_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE); + + self.owner.impulse = 141; // returning! + + tmp_entity = self; + self = self.owner; + ctf_RespawnFlag(tmp_entity); + ImpulseCommands(); + self = tmp_entity; + } + return; + + default: // this should never happen + dprint("Think: Flag exists with no status?\n"); + return; + } +} + +void ctf_FlagTouch() +{ + if(gameover) { return; } + if(!self) { return; } + if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) + { // The flag fell off the map, respawn it since players can't get to it + //ctf_RespawnFlag(self); + return; + } + if(other.deadflag != DEAD_NO) { return; } + if(other.classname != "player") + { // The flag just touched an object, most likely the world + pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1); - sound(self, CHAN_AUTO, "keepaway/touch.wav", VOL_BASE, ATTN_NORM); ++ sound(self, CH_TRIGGER, "keepaway/touch.wav", VOL_BASE, ATTN_NORM); + return; + } + else if(self.wait > time) { return; } + + switch(self.ctf_status) + { + case FLAG_BASE: + if((other.team == self.team) && (other.flagcarried) && (other.flagcarried.team != self.team)) + ctf_Handle_Capture(self, other); // other just captured the enemies flag to his base + else if((other.team != self.team) && (!other.flagcarried) && (!other.ctf_captureshielded)) + ctf_Handle_Pickup_Base(self, other); // other just stole the enemies flag + break; + + case FLAG_DROPPED: + if(other.team == self.team) + ctf_Handle_Return(self, other); // other just returned his own flag + else if((!other.flagcarried) && ((other.playerid != self.ctf_dropperid) || (time > self.ctf_droptime + autocvar_g_balance_ctf_delay_collect))) + ctf_Handle_Pickup_Dropped(self, other); // other just picked up a dropped enemy flag + break; + + case FLAG_CARRY: + dprint("Someone touched a flag even though it was being carried?\n"); + break; + + default: // this should never happen + dprint("Touch: Flag exists with no status?\n"); + break; + } +} + +void ctf_RespawnFlag(entity flag) +{ + // reset the player (if there is one) + if((flag.owner) && (flag.owner.flagcarried == flag)) + { + WaypointSprite_Kill(flag.wps_flagcarrier); + flag.owner.flagcarried = world; + + if(flag.speedrunning) + ctf_FakeTimeLimit(flag.owner, -1); + } + + // reset the flag + setattachment(flag, world, ""); + setorigin(flag, flag.ctf_spawnorigin); // replace with flag.ctf_spawnorigin + flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS); + flag.takedamage = DAMAGE_NO; + flag.solid = SOLID_TRIGGER; + flag.velocity = '0 0 0'; + flag.angles = flag.mangle; + flag.ctf_status = FLAG_BASE; + flag.flags = FL_ITEM | FL_NOTARGET; + flag.owner = world; +} + +void ctf_Reset() +{ + if(self.owner) + if(self.owner.classname == "player") + ctf_Handle_Drop(self.owner); + + ctf_RespawnFlag(self); +} + +void ctf_SetupFlag(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc +{ + // declarations + teamnumber = fabs(teamnumber - bound(0, g_ctf_reverse, 1)); // if we were originally 1, this will become 0. If we were originally 0, this will become 1. + + // main setup + flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist // todo: find out if this can be simplified + ctf_worldflaglist = flag; + + setattachment(flag, world, ""); + + flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); + flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue) + flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough) + flag.classname = "item_flag_team"; + flag.target = "###item###"; // wut? + flag.flags = FL_ITEM | FL_NOTARGET; + flag.solid = SOLID_TRIGGER; + flag.velocity = '0 0 0'; + flag.ctf_status = FLAG_BASE; + flag.ctf_spawnorigin = flag.origin; + flag.mangle = flag.angles; + flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; + + if(flag.spawnflags & 1) // I don't understand what all this is about. + { + flag.noalign = TRUE; + flag.movetype = MOVETYPE_NONE; + print("This map was loaded with flags using MOVETYPE_NONE\n"); + } + else + { + flag.noalign = FALSE; + flag.movetype = MOVETYPE_TOSS; + print("This map was loaded with flags using MOVETYPE_TOSS\n"); + } + + flag.reset = ctf_Reset; + flag.touch = ctf_FlagTouch; + + // appearence + if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); } + setmodel (flag, flag.model); // precision set below + setsize(flag, FLAG_MIN, FLAG_MAX); + setorigin(flag, flag.origin); + if(!flag.scale) { flag.scale = 0.6; } + + flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); + + if(autocvar_g_ctf_flag_glowtrails) + { + flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue + 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) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); } + + // sound + if(!flag.noise) { flag.noise = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); } + if(!flag.noise1) { flag.noise1 = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); } + if(!flag.noise2) { flag.noise2 = ((teamnumber) ? "ctf/red_capture.wav" : "ctf/blue_capture.wav"); } // blue team scores by capturing the red flag + if(!flag.noise3) { flag.noise3 = "ctf/flag_respawn.wav"; } // if there is ever a team-based sound for this, update the code to match. + if(!flag.noise4) { flag.noise4 = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); } + + // precache + precache_sound(flag.noise); + precache_sound(flag.noise1); + precache_sound(flag.noise2); + precache_sound(flag.noise3); + precache_sound(flag.noise4); + precache_model(flag.model); + precache_model("models/ctf/shield.md3"); + precache_model("models/ctf/shockwavetransring.md3"); + + // bot waypoints + waypoint_spawnforitem_force(flag, flag.origin); + flag.nearestwaypointtimeout = 0; // activate waypointing again + flag.bot_basewaypoint = flag.nearestwaypoint; + + // waypointsprites - WaypointSprite_SpawnFixed(((teamnumber) ? "redbase" : "bluebase"), flag.origin + '0 0 64', flag, wps_flagbase); ++ WaypointSprite_SpawnFixed(((teamnumber) ? "redbase" : "bluebase"), flag.origin + '0 0 64', flag, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE)); + WaypointSprite_UpdateTeamRadar(flag.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2) - 1, FALSE)); + + // captureshield setup + ctf_CaptureShield_Spawn(flag); +} + + +// ============== +// Hook Functions +// ============== + +// g_ctf_ignore_frags + +MUTATOR_HOOKFUNCTION(ctf_RemovePlayer) +{ + if(self.flagcarried) { ctf_Handle_Drop(self); } + return 0; +} + +MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink) +{ + entity flag; + + // initially clear items so they can be set as necessary later. + self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST + | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED); + + // item for stopping players from capturing the flag too often + if(self.ctf_captureshielded) + self.items |= IT_CTF_SHIELDED; + + // scan through all the flags and notify the client about them + for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) + { + if(flag.ctf_status == FLAG_CARRY) + if(flag.owner == self) + self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag + else + self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag + else if(flag.ctf_status == FLAG_DROPPED) + self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map + } + - if((autocvar_g_ctf_allow_drop) && (self.BUTTON_USE)) - ctf_Handle_Drop(self); - + return 0; +} + +MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc +{ /* + 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 noncarriers + { + frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor; + frag_force *= autocvar_g_ctf_flagcarrier_forcefactor; + } + }*/ + return 0; +} + +MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill) +{ + frag_score = 0; // no frags counted in keepaway + return (g_ctf_ignore_frags); // you deceptive little bugger ;3 This needs to be true in order for this function to even count. +} + ++MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey) ++{ ++ if(autocvar_g_ctf_allow_drop) ++ ctf_Handle_Drop(self); ++ ++ return 0; ++} + +// ========== +// Spawnfuncs +// ========== + +/*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24) +CTF Starting point for a player in team one (Red). +Keys: "angle" viewing angle when spawning. */ +void spawnfunc_info_player_team1() +{ + if(g_assault) { remove(self); return; } + + self.team = COLOR_TEAM1; // red + spawnfunc_info_player_deathmatch(); +} + + +/*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24) +CTF Starting point for a player in team two (Blue). +Keys: "angle" viewing angle when spawning. */ +void spawnfunc_info_player_team2() +{ + if(g_assault) { remove(self); return; } + + self.team = COLOR_TEAM2; // blue + spawnfunc_info_player_deathmatch(); +} + +/*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24) +CTF Starting point for a player in team three (Yellow). +Keys: "angle" viewing angle when spawning. */ +void spawnfunc_info_player_team3() +{ + if(g_assault) { remove(self); return; } + + self.team = COLOR_TEAM3; // yellow + spawnfunc_info_player_deathmatch(); +} + + +/*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24) +CTF Starting point for a player in team four (Purple). +Keys: "angle" viewing angle when spawning. */ +void spawnfunc_info_player_team4() +{ + if(g_assault) { remove(self); return; } + + self.team = COLOR_TEAM4; // purple + spawnfunc_info_player_deathmatch(); +} + +/*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37) +CTF flag for team one (Red). Multiple flags are allowed. +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 (default models/ctf/flag.md3)... +"noise" sound played when flag is picked up (default ctf/take.wav)... +"noise1" sound played when flag is returned by a teammate (default ctf/return.wav)... +"noise2" sound played when flag is captured (default ctf/redcapture.wav)... +"noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav)... */ +void spawnfunc_item_flag_team1() +{ + if(!g_ctf) { remove(self); return; } + + ctf_SetupFlag(1, self); // 1 = red +} + +/*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37) +CTF flag for team two (Blue). Multiple flags are allowed. +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 (default models/ctf/flag.md3)... +"noise" sound played when flag is picked up (default ctf/take.wav)... +"noise1" sound played when flag is returned by a teammate (default ctf/return.wav)... +"noise2" sound played when flag is captured (default ctf/redcapture.wav)... +"noise3" sound played when flag is lost in the field and respawns itself (default ctf/respawn.wav)... */ +void spawnfunc_item_flag_team2() +{ + if(!g_ctf) { remove(self); return; } + + ctf_SetupFlag(0, self); // the 0 is misleading, but -- 0 = blue. +} + +/*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)... */ +void spawnfunc_ctf_team() +{ + if(!g_ctf) { remove(self); return; } + + self.classname = "ctf_team"; + self.team = self.cnt + 1; +} + + +// ============== +// Initialization +// ============== + +// code from here on is just to support maps that don't have flag and team entities +void ctf_SpawnTeam (string teamname, float teamcolor) +{ + entity oldself; + oldself = self; + self = spawn(); + self.classname = "ctf_team"; + self.netname = teamname; + self.cnt = teamcolor; + + spawnfunc_ctf_team(); + + self = oldself; +} + +void ctf_DelayedInit() // 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(world, classname, "ctf_team") == world) + { + print("NO TEAMS FOUND FOR CTF! creating them anyway.\n"); + ctf_SpawnTeam("Red", COLOR_TEAM1 - 1); + ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1); + } + + ScoreRules_ctf(); +} + +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; + + g_ctf_win_mode = cvar("g_ctf_win_mode"); + + InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE); +} + + +MUTATOR_DEFINITION(gamemode_ctf) +{ + MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerDies, ctf_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY); ++ MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY); + //MUTATOR_HOOK(PlayerPowerups, ctf_PlayerPowerups, CBC_ORDER_ANY); + + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + g_ctf = 1; + ctf_Initialize(); + } + + MUTATOR_ONREMOVE + { + g_ctf = 0; + error("This is a game type and it cannot be removed at runtime."); + } + + return 0; +} diff --cc qcsrc/server/vehicles/vehicles.qc index 7cc2949f6,d9d77b5f5..2aeab3950 --- a/qcsrc/server/vehicles/vehicles.qc +++ b/qcsrc/server/vehicles/vehicles.qc @@@ -1,44 -1,652 +1,656 @@@ - void vehicle_stdproc_enter() + float autocvar_g_vehicles_crush_dmg; + float autocvar_g_vehicles_crush_force; + float autocvar_g_vehicles_delayspawn; + float autocvar_g_vehicles_delayspawn_jitter; + float autocvar_g_vehicles_allow_flagcarry; + + void vehicles_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force); + void vehicles_return(); + void vehicles_enter(); + void vehicles_touch(); + void vehicles_reset_colors(); + void vehicles_clearrturn(); + void vehicles_setreturn(); + + + /** AuxiliaryXhair* + Send additional points of interest to be drawn, to vehicle owner + **/ + float MAX_AXH = 4; + .entity AuxiliaryXhair[MAX_AXH]; + + float SendAuxiliaryXhair(entity to, float sf) + { + + WriteByte(MSG_ENTITY, ENT_CLIENT_AUXILIARYXHAIR); + + WriteByte(MSG_ENTITY, self.cnt); + + WriteCoord(MSG_ENTITY, self.origin_x); + WriteCoord(MSG_ENTITY, self.origin_y); + WriteCoord(MSG_ENTITY, self.origin_z); + + WriteByte(MSG_ENTITY, rint(self.colormod_x * 255)); + WriteByte(MSG_ENTITY, rint(self.colormod_y * 255)); + WriteByte(MSG_ENTITY, rint(self.colormod_z * 255)); + + return TRUE; + } + + void UpdateAuxiliaryXhair(entity own, vector loc, vector clr, float axh_id) + { + entity axh; + + axh_id = bound(0, axh_id, MAX_AXH); + axh = own.AuxiliaryXhair[axh_id]; + + if(axh == world || wasfreed(axh)) // MADNESS? THIS IS QQQQCCCCCCCCC (wasfreed, why do you exsist?) + { + axh = spawn(); + axh.cnt = axh_id; + axh.drawonlytoclient = own; + axh.owner = own; + Net_LinkEntity(axh, FALSE, 0, SendAuxiliaryXhair); + } + + setorigin(axh, loc); + axh.colormod = clr; + axh.SendFlags = 0x01; + own.AuxiliaryXhair[axh_id] = axh; + } + + /* + // SVC_TEMPENTITY based, horrible with even 50 ping. hm. + // WriteByte(MSG_ONE, SVC_TEMPENTITY) uses reliable messagess, never use for thinsg that need continous updates. + void SendAuxiliaryXhair2(entity own, vector loc, vector clr, float axh_id) { + msg_entity = own; + + WriteByte(MSG_ONE, SVC_TEMPENTITY); + WriteByte(MSG_ONE, TE_CSQC_AUXILIARYXHAIR); + + WriteByte(MSG_ONE, axh_id); + + WriteCoord(MSG_ONE, loc_x); + WriteCoord(MSG_ONE, loc_y); + WriteCoord(MSG_ONE, loc_z); + + WriteByte(MSG_ONE, rint(clr_x * 255)); + WriteByte(MSG_ONE, rint(clr_y * 255)); + WriteByte(MSG_ONE, rint(clr_z * 255)); + } + */ + // End AuxiliaryXhair - void vehicle_stdproc_exit(float eject) + /** + Notifies the client that he enterd a vehicle, and sends + realavent data. + + only sends vehicle_id atm (wich is a HUD_* constant, ex. HUD_SPIDERBOT) + **/ + void CSQCVehicleSetup(entity own, float vehicle_id) { + msg_entity = own; + + WriteByte(MSG_ONE, SVC_TEMPENTITY); + WriteByte(MSG_ONE, TE_CSQC_VEHICLESETUP); + WriteByte(MSG_ONE, vehicle_id); } - void vehicle_stdproc_shiledregen(float rmax, float dt) + /** vehicles_locktarget + + Generic target locking. + + Figure out if what target is "locked" (if any), for missile tracking as such. + + after calling, "if(self.lock_target != world && self.lock_strength == 1)" mean + you have a locked in target. + + Exspects a crosshair_trace() or equivalent to be + dont before calling. + + **/ + .entity lock_target; + .float lock_strength; + .float lock_time; + .float lock_soundtime; + void vehicles_locktarget(float incr, float decr, float _lock_time) { - if(self.vehicle_shield < rmax) - if(self.dmg_time + CCVAR("_shield_regen_dmgpause") < time) + if(self.lock_target && self.lock_target.deadflag != DEAD_NO) { - self.vehicle_shield = min(self.vehicle_shield + CCVAR("_shield_regen") * dt, rmax); + self.lock_target = world; + self.lock_strength = 0; + self.lock_time = 0; + } - if(self.owner) - self.owner.vehicle_shield = self.vehicle_shield / rmax; + if(self.lock_time > time) + { + if(self.lock_target) + if(self.lock_soundtime < time) + { + self.lock_soundtime = time + 0.5; + play2(self.owner, "vehicles/locked.wav"); + } + + return; } + + if(trace_ent != world) + { + if(teamplay && trace_ent.team == self.team) + trace_ent = world; + + if(trace_ent.deadflag != DEAD_NO) + trace_ent = world; + + if not (trace_ent.vehicle_flags & VHF_ISVEHICLE || trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET) + trace_ent = world; + } + + if(self.lock_target == world && trace_ent != world) + self.lock_target = trace_ent; + + if(self.lock_target && trace_ent == self.lock_target) + { + if(self.lock_strength != 1 && self.lock_strength + incr >= 1) + { + play2(self.owner, "vehicles/lock.wav"); + self.lock_soundtime = time + 0.8; + } + else if (self.lock_strength != 1 && self.lock_soundtime < time) + { + play2(self.owner, "vehicles/locking.wav"); + self.lock_soundtime = time + 0.3; + } + + } + + // Have a locking target + // Trace hit current target + if(trace_ent == self.lock_target && trace_ent != world) + { + self.lock_strength = min(self.lock_strength + incr, 1); + if(self.lock_strength == 1) + self.lock_time = time + _lock_time; + } + else + { + if(trace_ent) + self.lock_strength = max(self.lock_strength - decr * 2, 0); + else + self.lock_strength = max(self.lock_strength - decr, 0); + + if(self.lock_strength == 0) + self.lock_target = world; + } + } + + #define VEHICLE_UPDATE_PLAYER(fld,vhname) \ + self.owner.vehicle_##fld = (self.vehicle_##fld / autocvar_g_vehicle_##vhname##_##fld) * 100 + + #define vehicles_sweap_collision(orig,vel,dt,acm,mult) \ + traceline(orig, orig + vel * dt, MOVE_NORMAL, self); \ + if(trace_fraction != 1) \ + acm += normalize(self.origin - trace_endpos) * (vlen(vel) * mult) + + // Hover movement support + float force_fromtag_power; + float force_fromtag_normpower; + vector force_fromtag_origin; + vector vehicles_force_fromtag_hover(string tag_name, float spring_length, float max_power) + { + force_fromtag_origin = gettaginfo(self, gettagindex(self, tag_name)); + v_forward = normalize(v_forward) * -1; + traceline(force_fromtag_origin, force_fromtag_origin - (v_forward * spring_length), MOVE_NORMAL, self); + + force_fromtag_power = (1 - trace_fraction) * max_power; + force_fromtag_normpower = force_fromtag_power / max_power; + + return v_forward * force_fromtag_power; } - void vehicle_stdproc_healthregen(float rmax, float dt) + // Experimental hovermode wich uses attraction/repulstion from surface insted of gravity/repulsion + // Can possibly be use to move abt any surface (inclusing walls/celings) + vector vehicles_force_fromtag_maglev(string tag_name, float spring_length, float max_power) { - if(self.dmg_time + CCVAR("_health_regen_dmgpause") < time) - if(self.vehicle_health < rmax) + force_fromtag_origin = gettaginfo(self, gettagindex(self, tag_name)); + v_forward = normalize(v_forward) * -1; + traceline(force_fromtag_origin, force_fromtag_origin - (v_forward * spring_length), MOVE_NORMAL, self); + + // TODO - this may NOT be compatible with wall/celing movement, unhardcode 0.25 (engine count multiplier) + if(trace_fraction == 1.0) { - self.vehicle_health = min(self.vehicle_health + CCVAR("_health_regen") * dt, rmax); + force_fromtag_normpower = -0.25; + return '0 0 -200'; + } - if(self.owner) - self.owner.vehicle_health = self.vehicle_health / rmax; + force_fromtag_power = ((1 - trace_fraction) - trace_fraction) * max_power; + force_fromtag_normpower = force_fromtag_power / max_power; + + return v_forward * force_fromtag_power; + } + + // Generic vehile projectile system + void vehicles_projectile_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) + { + // Ignore damage from oterh projectiles from my owner (dont mess up volly's) + if(inflictor.owner == self.owner) + return; + + self.health -= damage; + self.velocity += force; + if(self.health < 1) + { + self.takedamage = DAMAGE_NO; + self.event_damage = SUB_Null; + self.think = self.use; + self.nextthink = time; } + + } + + void vehicles_projectile_explode() + { + if(self.owner && other != world) + { + if(other == self.owner.vehicle) + return; + + if(other == self.owner.vehicle.tur_head) + return; + } + + PROJECTILE_TOUCH; + + self.event_damage = SUB_Null; + RadiusDamage (self, self.realowner, self.shot_dmg, 0, self.shot_radius, self, self.shot_force, self.totalfrags, other); + + remove (self); + } + + entity vehicles_projectile(string _mzlfx, string _mzlsound, + vector _org, vector _vel, + float _dmg, float _radi, float _force, float _size, + float _deahtype, float _projtype, float _health, + float _cull, float _clianim) + { + entity proj; + + proj = spawn(); + + PROJECTILE_MAKETRIGGER(proj); + setorigin(proj, _org); + + proj.shot_dmg = _dmg; + proj.shot_radius = _radi; + proj.shot_force = _force; + proj.totalfrags = _deahtype; + proj.solid = SOLID_BBOX; + proj.movetype = MOVETYPE_FLYMISSILE; + proj.flags = FL_PROJECTILE; + proj.bot_dodge = TRUE; + proj.bot_dodgerating = _dmg; + proj.velocity = _vel; + proj.touch = vehicles_projectile_explode; + proj.use = vehicles_projectile_explode; + proj.owner = self; + proj.realowner = self.owner; + proj.think = SUB_Remove; + proj.nextthink = time + 30; + + if(_health) + { + proj.takedamage = DAMAGE_AIM; + proj.event_damage = vehicles_projectile_damage; + proj.health = _health; + } + else + proj.flags = FL_PROJECTILE | FL_NOTARGET; + + if(_mzlsound) + sound (self, CH_WEAPON_A, _mzlsound, VOL_BASE, ATTN_NORM); + + if(_mzlfx) + pointparticles(particleeffectnum(_mzlfx), proj.origin, proj.velocity, 1); + + + setsize (proj, '-1 -1 -1' * _size, '1 1 1' * _size); + + CSQCProjectile(proj, _clianim, _projtype, _cull); + + return proj; + } + // End generic vehile projectile system + + /** vehicles_spawn + Exetuted for all vehicles on (re)spawn. + Sets defaults for newly spawned units. + **/ + void vehicles_spawn() + { + dprint("Spawning vehicle: ", self.netname, "\n"); + + // De-own & reset + self.vehicle_hudmodel.viewmodelforclient = self; + + self.owner = world; + self.touch = vehicles_touch; + self.event_damage = vehicles_damage; + self.iscreature = TRUE; + self.movetype = MOVETYPE_WALK; + self.solid = SOLID_SLIDEBOX; + self.takedamage = DAMAGE_AIM; + self.deadflag = DEAD_NO; + self.bot_attack = TRUE; + self.flags = FL_NOTARGET; + self.avelocity = '0 0 0'; + self.velocity = '0 0 0'; + + // Reset locking + self.lock_strength = 0; + self.lock_target = world; + self.misc_bulletcounter = 0; + + // Return to spawn + self.angles = self.pos2; + setorigin(self, self.pos1 + '0 0 128'); + // Show it + pointparticles(particleeffectnum("teleport"), self.origin + '0 0 64', '0 0 0', 1); + + vehicles_reset_colors(); + self.vehicle_spawn(); } - void vehicle_stdproc_energyregen(float rmax, float dt) + // Better way of determening whats crushable needed! (fl_crushable?) + float vehicles_crushable(entity e) { - if(self.vehicle_energy < rmax) + if(e.classname == "player") + return TRUE; + + if(e.classname == "monster_zombie") + return TRUE; + + return FALSE; + } + + void vehicles_touch() + { + // Vehicle currently in use + if(self.owner) { - self.vehicle_energy = min(self.vehicle_energy + CCVAR("_energy_regen") * dt, rmax); + // Colided with world? + if(other == world) + { + } + else + { + if(other.vehicle_flags & VHF_ISVEHICLE) + { + //other.velocity += self.velocity * (self.mass / other.mass); + } + else if(vehicles_crushable(other)) + { + if(vlen(self.velocity) != 0) + Damage(other, self, self.owner, autocvar_g_vehicles_crush_dmg, DEATH_VHCRUSH, '0 0 0', normalize(other.origin - self.origin) * autocvar_g_vehicles_crush_force); + } + } + return; + } + + if(other.classname != "player") + return; + + if(other.deadflag != DEAD_NO) + return; + + if(other.vehicle != world) + return; + + // Remove this when bots know how to use vehicles. + if (clienttype(other) != CLIENTTYPE_REAL) + return; + + vehicles_enter(); + } + + void vehicles_enter() + { + // Remove this when bots know how to use vehicles + if (clienttype(other) != CLIENTTYPE_REAL) + return; + + if(self.phase > time) + return; + + if(teamplay) + if(self.team) + if(self.team != other.team) + return; + + self.vehicle_ammo1 = 0; + self.vehicle_ammo2 = 0; + self.vehicle_reload1 = 0; + self.vehicle_reload2 = 0; + self.vehicle_energy = 0; + + self.owner = other; + self.switchweapon = other.switchweapon; + + // .viewmodelforclient works better. + //self.vehicle_hudmodel.drawonlytoclient = self.owner; + + self.vehicle_hudmodel.viewmodelforclient = self.owner; + + self.event_damage = vehicles_damage; + self.nextthink = 0; + self.owner.angles = self.angles; + self.owner.takedamage = DAMAGE_NO; + self.owner.solid = SOLID_NOT; + self.owner.movetype = MOVETYPE_NOCLIP; + self.owner.alpha = -1; + self.owner.vehicle = self; + self.owner.event_damage = SUB_Null; + self.owner.view_ofs = '0 0 0'; + self.colormap = self.owner.colormap; + if(self.tur_head) + self.tur_head.colormap = self.owner.colormap; + + self.owner.hud = self.hud; + self.owner.PlayerPhysplug = self.PlayerPhysplug; + + self.owner.vehicle_ammo1 = self.vehicle_ammo1; + self.owner.vehicle_ammo2 = self.vehicle_ammo2; + self.owner.vehicle_reload1 = self.vehicle_reload1; + self.owner.vehicle_reload2 = self.vehicle_reload2; + + // Cant do this, hides attached objects too. + //self.exteriormodeltoclient = self.owner; + //self.tur_head.exteriormodeltoclient = self.owner; + + other.flags &~= FL_ONGROUND; + self.flags &~= FL_ONGROUND; + + self.team = self.owner.team; + self.flags -= FL_NOTARGET; + + msg_entity = other; + WriteByte (MSG_ONE, SVC_SETVIEWPORT); + WriteEntity(MSG_ONE, self.vehicle_viewport); + + WriteByte (MSG_ONE, SVC_SETVIEWANGLES); + if(self.tur_head) + { + WriteAngle(MSG_ONE, self.tur_head.angles_x + self.angles_x); // tilt + WriteAngle(MSG_ONE, self.tur_head.angles_y + self.angles_y); // yaw + WriteAngle(MSG_ONE, 0); // roll + } + else + { + WriteAngle(MSG_ONE, self.angles_x * -1); // tilt + WriteAngle(MSG_ONE, self.angles_y); // yaw + WriteAngle(MSG_ONE, 0); // roll + } + + vehicles_clearrturn(); + + CSQCVehicleSetup(self.owner, self.hud); + ++ /* FIXCTF // THIS IS A BIG NO-NO, NO GAME MODE SPECIFIC CODE IN VEHICLES. + if(other.flagcarried) + { + if(!autocvar_g_vehicles_allow_flagcarry) + DropFlag(other.flagcarried, world, world); + else + { + other.flagcarried.scale = 1; + setattachment(other.flagcarried, self, ""); + setorigin(other, '0 0 96'); + } + } ++ */ + + self.vehicle_enter(); + } + + /** vehicles_findgoodexit + Locates a valid location for the player to exit the vehicle. + Will first try prefer_spot, then up 100 random spots arround the vehicle + wich are in direct line of sight and empty enougth to hold a players bbox + **/ + vector vehicles_findgoodexit(vector prefer_spot) + { + //vector exitspot; + float mysize; + + tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, prefer_spot, MOVE_NORMAL, self.owner); + if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid) + return prefer_spot; + + mysize = vlen(self.maxs - self.mins); + float i; + vector v, v2; + v2 = 0.5 * (self.absmin + self.absmax); + for(i = 0; i < 100; ++i) + { + v = randomvec(); + v_z = 0; + v = v2 + normalize(v) * mysize; + tracebox(v2, PL_MIN, PL_MAX, v, MOVE_NORMAL, self.owner); + if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid) + return v; + } + + /* + exitspot = (self.origin + '0 0 48') + v_forward * mysize; + tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner); + if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid) + return exitspot; + + exitspot = (self.origin + '0 0 48') - v_forward * mysize; + tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner); + if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid) + return exitspot; + + exitspot = (self.origin + '0 0 48') + v_right * mysize; + tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner); + if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid) + return exitspot; + + exitspot = (self.origin + '0 0 48') - v_right * mysize; + tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner); + if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid) + return exitspot; + */ + + return self.origin; + } + + /** vehicles_exit + Standarrd vehicle release fucntion. + custom code goes in self.vehicle_exit + **/ + void vehicles_exit(float eject) + { + entity oldself; + if(self.flags & FL_CLIENT) + { + oldself = self; + self = self.vehicle; + } + + self.flags |= FL_NOTARGET; + + if (self.owner) + { + msg_entity = self.owner; + WriteByte (MSG_ONE, SVC_SETVIEWPORT); + WriteEntity( MSG_ONE, self.owner); + + WriteByte (MSG_ONE, SVC_SETVIEWANGLES); + WriteAngle(MSG_ONE, 0); // pich + WriteAngle(MSG_ONE, self.angles_y); // yaw + WriteAngle(MSG_ONE, 0); // roll + + setsize(self.owner, PL_MIN,PL_MAX); + + self.owner.takedamage = DAMAGE_AIM; + self.owner.solid = SOLID_SLIDEBOX; + self.owner.movetype = MOVETYPE_WALK; + self.owner.effects &~= EF_NODRAW; + self.owner.alpha = 1; + self.owner.PlayerPhysplug = SUB_Null; + self.owner.vehicle = world; + self.owner.view_ofs = PL_VIEW_OFS; + self.owner.event_damage = PlayerDamage; + self.owner.hud = HUD_NORMAL; + self.owner.switchweapon = self.switchweapon; + //self.owner.BUTTON_USE = 0; + } + + if(self.deadflag == DEAD_NO) + self.avelocity = '0 0 0'; + + self.vehicle_hudmodel.viewmodelforclient = self; + self.tur_head.nodrawtoclient = world; + vehicles_setreturn(); + + self.phase = time + 1; + + if(!teamplay) + self.team = 0; + else + self.team = self.tur_head.team; + ++ /* FIXCTF // THIS IS A BIG NO-NO, NO GAME MODE SPECIFIC CODE IN VEHICLES. + if(self.owner.flagcarried) + { + self.owner.flagcarried.scale = 0.6; + setattachment(self.owner.flagcarried, self.owner, ""); + setorigin(self.owner.flagcarried, FLAG_CARRY_POS); + } ++ */ + + sound (self, CH_TRIGGER_SINGLE, "misc/null.wav", 1, ATTN_NORM); + self.vehicle_exit(eject); + self.owner = world; + vehicles_reset_colors(); + + if(oldself) + self = oldself; + } + + + void vehicles_regen(.float timer, .float regen_field, float field_max, float rpause, float regen, float delta_time) + { + if(self.regen_field < field_max) + if(self.timer + rpause < time) + { + self.regen_field = min(self.regen_field + regen * delta_time, field_max); if(self.owner) - self.owner.vehicle_energy = self.vehicle_energy / rmax; + self.owner.regen_field = (self.regen_field / field_max) * 100; } }