From: Samual Date: Sun, 1 Apr 2012 00:24:25 +0000 (-0400) Subject: Implement whole new passing/throwing system which *actually* throws the flag to team... X-Git-Tag: xonotic-v0.7.0~240^2~132 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=adda0be6aaeb7a2d32c5e4b0b1584fc866807464;p=xonotic%2Fxonotic-data.pk3dir.git Implement whole new passing/throwing system which *actually* throws the flag to team mates, allows for interception by enemies and has a cool animation. --- diff --git a/defaultXonotic.cfg b/defaultXonotic.cfg index f723560b4..0b03ce261 100644 --- a/defaultXonotic.cfg +++ b/defaultXonotic.cfg @@ -609,10 +609,11 @@ set g_ctf_flag_pickup_verbosename 1 "show the name of the person who picked up t set g_ctf_flag_return_when_unreachable 1 "automatically return the flag if it falls into lava/slime/trigger hurt" set g_ctf_throw_velocity 500 "how far a player can throw the flag" set g_ctf_allow_pass 1 "allow passing of flags to nearby team mates" -set g_ctf_pass_radius 200 "maximum radius that you can pass to a team mate in" +set g_ctf_pass_radius 300 "maximum radius that you can pass to a team mate in" set g_ctf_pass_wait 2 "delay in seconds between how often players can pass the flag (antispam, essentially)" set g_ctf_dropped_capture_radius 100 "allow dropped flags to be automatically captured by base flags if the dropped flag is within this radius of it" set g_ctf_pass_request 1 "allow players to request the flag carrier to pass the flag to them" +set g_ctf_pass_turnrate 50 "how well the flag follows the best direction to its target while passing" set g_ctf_shield_max_ratio 0 "shield at most this percentage of a team from the enemy flag (try: 0.4 for 40%)" set g_ctf_shield_min_negscore 20 "shield the player from the flag if he's got this negative amount of points or less" diff --git a/qcsrc/server/autocvars.qh b/qcsrc/server/autocvars.qh index fa8661673..8d70b4067 100644 --- a/qcsrc/server/autocvars.qh +++ b/qcsrc/server/autocvars.qh @@ -765,6 +765,7 @@ float autocvar_g_ctf_allow_pass; float autocvar_g_ctf_pass_radius; float autocvar_g_ctf_pass_wait; float autocvar_g_ctf_pass_request; +float autocvar_g_ctf_pass_turnrate; float autocvar_g_ctf_throw_velocity; float autocvar_g_ctf_captimerecord_always; float autocvar_g_ctf_dynamiclights; diff --git a/qcsrc/server/mutators/gamemode_ctf.qc b/qcsrc/server/mutators/gamemode_ctf.qc index 1fa260ef8..6b731955b 100644 --- a/qcsrc/server/mutators/gamemode_ctf.qc +++ b/qcsrc/server/mutators/gamemode_ctf.qc @@ -172,12 +172,12 @@ void ctf_CaptureShield_Spawn(entity flag) // Drop/Pass/Throw Code // ==================== -void ctf_Handle_Recieve(entity player, entity reciever) +void ctf_Handle_Failed_Pass(entity flag) { - entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players - entity flag = player.flagcarried; + /*entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players + entity sender = flag.pass_sender; if(!flag) { return; } - if(time < flag.pass_antispam) { return; } // antispam of passing + if(time < flag.throw_antispam) { return; } // antispam of passing // reset player player.flagcarried = world; @@ -187,7 +187,7 @@ void ctf_Handle_Recieve(entity player, entity reciever) // transfer flag to reciever flag.owner = reciever; flag.owner.flagcarried = flag; - flag.ctf_pickupper = reciever; + flag.ctf_carrier = reciever; setattachment(flag, reciever, ""); setorigin(flag, FLAG_CARRY_OFFSET); @@ -212,58 +212,65 @@ void ctf_Handle_Recieve(entity player, entity reciever) WaypointSprite_UpdateHealth(reciever.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(reciever.health, reciever.armorvalue, autocvar_g_balance_armor_blockpercent)); WaypointSprite_UpdateTeamRadar(reciever.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0'); - flag.pass_antispam = time + autocvar_g_ctf_pass_wait; + flag.throw_antispam = time + autocvar_g_ctf_pass_wait;*/ + + print("ctf_Handle_Failed_Pass() called.\n"); + ctf_RespawnFlag(flag); } -void ctf_Handle_Pass(entity player, entity reciever) +void ctf_Handle_Retrieve(entity flag, entity player) { entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players - entity flag = player.flagcarried; - if(!flag) { return; } - if(time < flag.pass_antispam) { return; } // antispam of passing + entity sender = flag.pass_sender; - // reset player - player.flagcarried = world; - WaypointSprite_Ping(player.wps_flagcarrier); - WaypointSprite_Kill(player.wps_flagcarrier); - - // transfer flag to reciever - flag.owner = reciever; + // transfer flag to player + flag.ctf_carrier = player; + flag.owner = player; flag.owner.flagcarried = flag; - flag.ctf_pickupper = reciever; - setattachment(flag, reciever, ""); + + // reset flag + setattachment(flag, player, ""); setorigin(flag, FLAG_CARRY_OFFSET); + flag.movetype = MOVETYPE_NONE; + flag.takedamage = DAMAGE_NO; + flag.solid = SOLID_NOT; + flag.ctf_carrier = player; + flag.ctf_status = FLAG_CARRY; // messages and sounds - sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM); - ctf_EventLog("pass", flag.team, player); - ctf_EventLog("recieve", flag.team, reciever); + sound(player, CH_TRIGGER, "keepaway/respawn.wav", VOL_BASE, ATTN_NORM); + ctf_EventLog("recieve", flag.team, player); FOR_EACH_PLAYER(tmp_player) - if(tmp_player == player) - centerprint(tmp_player, strcat("You passed the ", flag.netname, " to ", reciever.netname)); - else if(tmp_player == reciever) - centerprint(tmp_player, strcat("You recieved the ", flag.netname, " from ", player.netname)); - else if(tmp_player.team == player.team) - centerprint(tmp_player, strcat(player.netname, " passed the ", flag.netname, " to ", reciever.netname)); - - // effects - te_lightning2(world, reciever.origin, player.origin); + if(tmp_player == sender) + centerprint(tmp_player, strcat("You passed the ", flag.netname, " to ", player.netname)); + else if(tmp_player == player) + centerprint(tmp_player, strcat("You recieved the ", flag.netname, " from ", sender.netname)); + else if(tmp_player.team == sender.team) + centerprint(tmp_player, strcat(sender.netname, " passed the ", flag.netname, " to ", player.netname)); // create new waypoint - WaypointSprite_Spawn("flagcarrier", 0, 0, reciever, '0 0 64', world, reciever.team, reciever, wps_flagcarrier, FALSE, RADARICON_FLAG, '1 1 0'); - WaypointSprite_UpdateMaxHealth(reciever.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2); - WaypointSprite_UpdateHealth(reciever.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(reciever.health, reciever.armorvalue, autocvar_g_balance_armor_blockpercent)); - WaypointSprite_UpdateTeamRadar(reciever.wps_flagcarrier, RADARICON_FLAGCARRIER, '1 1 0'); + WaypointSprite_Spawn("flagcarrier", 0, 0, player, '0 0 64', world, player.team, player, wps_flagcarrier, FALSE, RADARICON_FLAG, '1 1 0'); + 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'); + + sender.throw_antispam = time + autocvar_g_ctf_pass_wait; + player.throw_antispam = sender.throw_antispam; + + flag.pass_sender = world; + flag.pass_target = world; - flag.pass_antispam = time + autocvar_g_ctf_pass_wait; + print("ctf_Handle_Retrieve() called.\n"); } -void ctf_Handle_Drop(entity player, float droptype) +void ctf_Handle_Throw(entity player, entity reciever, float droptype) { entity flag = player.flagcarried; + if(!flag) { return; } + if((droptype == DROPTYPE_PASS) && !reciever) { return; } - if(flag.speedrunning) { ctf_RespawnFlag(flag); return; } + //if(flag.speedrunning) { ctf_RespawnFlag(flag); return; } // reset the flag setattachment(flag, world, ""); @@ -272,12 +279,17 @@ void ctf_Handle_Drop(entity player, float droptype) flag.owner = world; flag.movetype = MOVETYPE_TOSS; flag.solid = SOLID_TRIGGER; - flag.takedamage = DAMAGE_YES; - flag.health = flag.max_flag_health; switch(droptype) { - case DROPTYPE_THROWN: + case DROPTYPE_PASS: + { + vector targ_origin = (0.5 * (reciever.absmin + reciever.absmax)); + flag.velocity = W_CalculateProjectileVelocity(player.velocity, ('0 0 200' + (normalize(targ_origin - player.origin) * autocvar_g_ctf_throw_velocity)), FALSE); + break; + } + + case DROPTYPE_THROW: { makevectors((player.v_angle_y * '0 1 0') + (player.v_angle_x * '0.5 0 0')); flag.velocity = W_CalculateProjectileVelocity(player.velocity, ('0 0 200' + (v_forward * autocvar_g_ctf_throw_velocity)), FALSE); @@ -285,47 +297,69 @@ void ctf_Handle_Drop(entity player, float droptype) } default: - case DROPTYPE_NORMAL: + case DROPTYPE_DROP: { flag.velocity = ('0 0 200' + ('0 100 0' * crandom()) + ('100 0 0' * crandom())); break; } } - flag.ctf_droptime = time; - flag.ctf_dropper = player; - flag.ctf_status = FLAG_DROPPED; + switch(droptype) + { + case DROPTYPE_PASS: + { + // main + flag.takedamage = DAMAGE_NO; + flag.pass_sender = player; + flag.pass_target = reciever; + flag.ctf_status = FLAG_PASSING; + + // other + sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM); + ctf_EventLog("pass", flag.team, player); + te_lightning2(world, reciever.origin, player.origin); + break; + } + + default: + case DROPTYPE_THROW: + case DROPTYPE_DROP: + { + // main + flag.takedamage = DAMAGE_YES; + flag.health = flag.max_flag_health; + flag.ctf_droptime = time; + flag.ctf_dropper = player; + flag.ctf_status = FLAG_DROPPED; + + // messages and sounds + Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO); + sound(flag, CH_TRIGGER, flag.snd_flag_dropped, 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 + if(autocvar_g_ctf_flag_dropped_waypoint) + WaypointSprite_Spawn("flagdropped", 0, 0, flag, '0 0 64', world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, FALSE, RADARICON_FLAG, '0 0.5 0' + ((flag.team == COLOR_TEAM1) ? '0.75 0 0' : '0 0 0.75')); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team) - // messages and sounds - Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO); - sound(flag, CH_TRIGGER, flag.snd_flag_dropped, 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); + if(autocvar_g_ctf_flag_returntime || (autocvar_g_ctf_flag_take_damage && autocvar_g_ctf_flag_health)) + { + WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health); + WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); + } + break; + } + } - // waypoints - if(autocvar_g_ctf_flag_dropped_waypoint) - WaypointSprite_Spawn("flagdropped", 0, 0, flag, '0 0 64', world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, FALSE, RADARICON_FLAG, '0 0.5 0' + ((flag.team == COLOR_TEAM1) ? '0.75 0 0' : '0 0 0.75')); // (COLOR_TEAM1 + COLOR_TEAM2 - flag.team) - + // kill old waypointsprite WaypointSprite_Ping(player.wps_flagcarrier); WaypointSprite_Kill(player.wps_flagcarrier); - - if(autocvar_g_ctf_flag_returntime || (autocvar_g_ctf_flag_take_damage && autocvar_g_ctf_flag_health)) - { - WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health); - WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); - } - + // 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"); + //ctf_CaptureShield_Update(player, 0); // shield only } @@ -425,7 +459,7 @@ void ctf_Handle_Pickup_Base(entity flag, entity player) flag.solid = SOLID_NOT; flag.angles = '0 0 0'; flag.ctf_pickuptime = time; // used for timing runs - flag.ctf_pickupper = player; + flag.ctf_carrier = player; flag.ctf_status = FLAG_CARRY; // messages and sounds @@ -475,7 +509,7 @@ void ctf_Handle_Pickup_Dropped(entity flag, entity player) 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_pickupper = player; + flag.ctf_carrier = player; flag.ctf_status = FLAG_CARRY; // messages and sounds @@ -603,7 +637,23 @@ void ctf_FlagThink() case FLAG_PASSING: { - dprint("Someone touched a flag even though it was being carried?\n"); + traceline(self.origin, self.pass_target.origin, MOVE_NOMONSTERS, self); + + vector targ_origin = (0.5 * (self.pass_target.absmin + self.pass_target.absmax)); + + if((self.pass_target.deadflag != DEAD_NO) + || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius) + || ((trace_fraction < 1) && (trace_ent != self.pass_target))) + { + ctf_Handle_Failed_Pass(self); + } + else // still a viable target, go for it + { + vector desired_direction = normalize(targ_origin - self.origin); + vector current_direction = normalize(self.velocity); + + self.velocity = (normalize(current_direction + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_throw_velocity); + } return; } @@ -665,7 +715,13 @@ void ctf_FlagTouch() case FLAG_PASSING: { - dprint("Someone touched a flag even though it was being carried?\n"); + if((other.classname == "player") && (other.deadflag == DEAD_NO) && (other != self.pass_sender)) + { + if(IsDifferentTeam(other, self.pass_sender)) + ctf_Handle_Return(self, other); + else + ctf_Handle_Retrieve(self, other); + } break; } @@ -696,23 +752,31 @@ void ctf_RespawnFlag(entity flag) // reset the flag setattachment(flag, world, ""); - setorigin(flag, flag.ctf_spawnorigin); // replace with flag.ctf_spawnorigin + setorigin(flag, flag.ctf_spawnorigin); + flag.movetype = ((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.ctf_status = FLAG_BASE; flag.flags = FL_ITEM | FL_NOTARGET; + + flag.ctf_status = FLAG_BASE; flag.owner = world; + flag.pass_sender = world; + flag.pass_target = world; + flag.ctf_carrier = world; + flag.ctf_dropper = world; + flag.ctf_pickuptime = 0; + flag.ctf_droptime = 0; } void ctf_Reset() { if(self.owner) if(self.owner.classname == "player") - ctf_Handle_Drop(self.owner, DROPTYPE_NORMAL); + ctf_Handle_Throw(self.owner, world, DROPTYPE_DROP); ctf_RespawnFlag(self); } @@ -834,7 +898,7 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag MUTATOR_HOOKFUNCTION(ctf_HookedDrop) { - if(self.flagcarried) { ctf_Handle_Drop(self, DROPTYPE_NORMAL); } + if(self.flagcarried) { ctf_Handle_Throw(self, world, DROPTYPE_DROP); } return 0; } @@ -897,39 +961,43 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey) { entity player = self; - // pass the flag to a team mate - if(autocvar_g_ctf_allow_pass && (time > player.pass_antispam)) + if(time > player.throw_antispam) { - entity head, closest_target; - head = findradius(player.origin, autocvar_g_ctf_pass_radius); - - while(head) // find the closest acceptable target to pass to + // pass the flag to a team mate + if(autocvar_g_ctf_allow_pass) { - if(head.classname == "player" && head.deadflag == DEAD_NO) - if(head != player && !IsDifferentTeam(head, player)) - if(!player.speedrunning && !head.speedrunning) + entity head, closest_target; + head = findradius(player.origin, autocvar_g_ctf_pass_radius); + + while(head) // find the closest acceptable target to pass to { - if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried) - { - centerprint(head, strcat(player.netname, " requests you to pass the ", head.flagcarried.netname)); - player.pass_antispam = time + autocvar_g_ctf_pass_wait; - return 0; - } - else if(player.flagcarried) + if(head.classname == "player" && head.deadflag == DEAD_NO) + if(head != player && !IsDifferentTeam(head, player)) + if(!player.speedrunning && !head.speedrunning) { - if(closest_target) { if(vlen(player.origin - head.origin) < vlen(player.origin - closest_target.origin)) { closest_target = head; } } - else { closest_target = head; } + if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried) + { + centerprint(head, strcat(player.netname, " requests you to pass the ", head.flagcarried.netname)); + centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname)); + player.throw_antispam = time + autocvar_g_ctf_pass_wait; + return 0; + } + else if(player.flagcarried) + { + if(closest_target) { if(vlen(player.origin - head.origin) < vlen(player.origin - closest_target.origin)) { closest_target = head; } } + else { closest_target = head; } + } } + head = head.chain; } - head = head.chain; + + if(closest_target) { ctf_Handle_Throw(player, closest_target, DROPTYPE_PASS); return 0; } } - if(closest_target) { ctf_Handle_Pass(player, closest_target); return 0; } + // throw the flag in front of you + if(autocvar_g_ctf_allow_drop && player.flagcarried && !player.speedrunning) + { ctf_Handle_Throw(player, world, DROPTYPE_THROW); } } - - // throw the flag in front of you - if(autocvar_g_ctf_allow_drop && player.flagcarried && !player.speedrunning) - { ctf_Handle_Drop(player, DROPTYPE_THROWN); } return 0; } diff --git a/qcsrc/server/mutators/gamemode_ctf.qh b/qcsrc/server/mutators/gamemode_ctf.qh index ce39a04df..38f48b4e2 100644 --- a/qcsrc/server/mutators/gamemode_ctf.qh +++ b/qcsrc/server/mutators/gamemode_ctf.qh @@ -4,7 +4,7 @@ void ctf_RespawnFlag(entity flag) // used in portals.qc -void ctf_Handle_Drop(entity player, float droptype); +void ctf_Handle_Throw(entity player, entity reciever, float droptype); // used in g_damage.qc float ctf_ReadScore(string parameter); // SOON WON'T BE NEEDED. // FIXCTF @@ -58,8 +58,9 @@ entity ctf_worldflaglist; #define FLAG_CARRY 3 #define FLAG_PASSING 4 -#define DROPTYPE_NORMAL 1 -#define DROPTYPE_THROWN 2 +#define DROPTYPE_DROP 1 +#define DROPTYPE_THROW 2 +#define DROPTYPE_PASS 3 // flag properties #define ctf_spawnorigin dropped_origin @@ -67,12 +68,14 @@ float ctf_captimerecord; // record time for capturing the flag .float ctf_pickuptime; .float ctf_droptime; .float ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally) -.float max_flag_health; -.entity ctf_pickupper; +.entity ctf_carrier; .entity ctf_dropper; // don't allow spam of dropping the flag +.float max_flag_health; -// other properties -.float pass_antispam; +// passing properties +.entity pass_sender; +.entity pass_target; +.float throw_antispam; // 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