From: Mario Date: Fri, 14 Nov 2014 02:05:18 +0000 (+1100) Subject: Merge branch 'master' into Mario/weapons X-Git-Tag: xonotic-v0.8.0~152^2~1 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=2b89f5bdd3be253aa44320dc71f70f283df3b5ee;p=xonotic%2Fxonotic-data.pk3dir.git Merge branch 'master' into Mario/weapons Conflicts: mutators.cfg qcsrc/client/hud.qc qcsrc/client/waypointsprites.qc qcsrc/common/constants.qh qcsrc/common/notifications.qh qcsrc/server/cl_weapons.qc qcsrc/server/mutators/mutator_nades.qc qcsrc/server/progs.src qcsrc/server/w_electro.qc qcsrc/server/w_minelayer.qc --- 2b89f5bdd3be253aa44320dc71f70f283df3b5ee diff --cc mutators.cfg index 0dde22eed,4b7904867..6873aa89e --- a/mutators.cfg +++ b/mutators.cfg @@@ -163,8 -231,49 +231,57 @@@ set g_campcheck_damage 10 set g_campcheck_distance 1800 ++<<<<<<< HEAD +// ========== +// New Toys +// ========== +set g_new_toys 0 "Mutator 'New Toys': enable extra fun guns" +set g_new_toys_autoreplace 2 "0: never replace, 1: always auto replace guns by available new toys, 2: randomly auto replace guns by available new toys" ++======= + // ======= + // buffs + // ======= + set cl_buffs_autoreplace 1 "automatically drop current buff when picking up another" + set g_buffs 0 "enable buffs (requires buff items or powerups)" + set g_buffs_waypoint_distance 1024 "maximum distance at which buff waypoint can be seen from item" + set g_buffs_randomize 1 "randomize buff type when player drops buff" + set g_buffs_random_lifetime 30 "re-spawn the buff again if it hasn't been touched after this time in seconds" + set g_buffs_random_location 0 "randomize buff location on start and when reset" + set g_buffs_random_location_attempts 10 "number of random locations a single buff will attempt to respawn at before giving up" + set g_buffs_spawn_count 5 "how many buffs to spawn on the map if none exist already" + set g_buffs_replace_powerups 1 "replace powerups on the map with random buffs" + set g_buffs_cooldown_activate 5 "cooldown period when buff is first activated" + set g_buffs_cooldown_respawn 3 "cooldown period when buff is reloading" + set g_buffs_ammo 1 "ammo buff: infinite ammunition" + set g_buffs_resistance 1 "resistance buff: greatly reduces damage taken" + set g_buffs_resistance_blockpercent 0.7 "damage reduction multiplier, higher values mean less damage" + set g_buffs_medic 1 "medic buff: increased regeneration speed, extra health, chance to survive a fatal attack" + set g_buffs_medic_survive_chance 0.6 "multiplier chance of player surviving a fatal hit" + set g_buffs_medic_survive_health 5 "amount of health player survives with after taking a fatal hit" + set g_buffs_medic_rot 0.7 "health rot rate multiplier" + set g_buffs_medic_max 1.5 "stable health medic limit multiplier" + set g_buffs_medic_regen 1.7 "health medic rate multiplier" + set g_buffs_vengeance 1 "vengeance buff: attackers also take damage" + set g_buffs_vengeance_damage_multiplier 0.6 "amount of damage dealt the attacker takes when hitting a target with vengeance" + set g_buffs_bash 1 "bash buff: increased knockback force and immunity to knockback" + set g_buffs_bash_force 2 "bash force multiplier" + set g_buffs_bash_force_self 1.2 "bash self force multiplier" + set g_buffs_disability 1 "disability buff: attacks to players and monsters deal slowness (decreased movement/attack speed) for a few seconds" + set g_buffs_disability_time 3 "time in seconds for target disability" + set g_buffs_disability_speed 0.5 "player speed multiplier while disabled" + set g_buffs_disability_rate 1.7 "player weapon rate multiplier while disabled" + set g_buffs_speed 1 "speed buff: increased movement/attack/health regeneration speed, carrier takes slightly more damage" + set g_buffs_speed_speed 1.7 "player speed multiplier while holding speed buff" + set g_buffs_speed_rate 0.8 "player weapon rate multiplier while holding speed buff" + set g_buffs_speed_damage_take 1.2 "damage taken multiplier while holding speed buff" + set g_buffs_speed_regen 1.2 "regeneration speed multiplier while holding speed buff" + set g_buffs_vampire 1 "vampire buff: attacks to players and monsters heal the carrier" + set g_buffs_vampire_damage_steal 0.6 "damage stolen multiplier while holding vampire buff" + set g_buffs_jump 1 "jump buff: greatly increased jump height" + set g_buffs_jump_height 600 "jump height while holding jump buff" + set g_buffs_flight 1 "flight buff: greatly decreased gravity" + set g_buffs_flight_gravity 0.3 "player gravity multiplier while holding flight buff" + set g_buffs_invisible 1 "invisible buff: carrier becomes invisible" + set g_buffs_invisible_alpha 0.4 "player invisibility multiplier while holding invisible buff" + ++>>>>>>> master diff --cc qcsrc/client/hud.qc index acd22fef8,ab61eb3c3..701990d6b --- a/qcsrc/client/hud.qc +++ b/qcsrc/client/hud.qc @@@ -831,30 -858,92 +831,78 @@@ void HUD_Weapons(void } // Ammo (#1) -// -// TODO: macro -float GetAmmoItemCode(float i) -{ - switch(i) - { - case 0: return IT_SHELLS; - case 1: return IT_NAILS; - case 2: return IT_ROCKETS; - case 3: return IT_CELLS; - case 4: return IT_FUEL; - default: return -1; - } -} - -string GetAmmoPicture(float i) -{ - switch(i) - { - case 0: return "ammo_shells"; - case 1: return "ammo_bullets"; - case 2: return "ammo_rockets"; - case 3: return "ammo_cells"; - case 4: return "ammo_fuel"; - default: return ""; - } -} - + void DrawNadeScoreBar(vector myPos, vector mySize, vector color) + { + + HUD_Panel_DrawProgressBar( + myPos + eX * autocvar_hud_panel_ammo_progressbar_xoffset * mySize_x, + mySize - eX * autocvar_hud_panel_ammo_progressbar_xoffset * mySize_x, + autocvar_hud_panel_ammo_progressbar_name, + getstatf(STAT_NADE_BONUS_SCORE), 0, 0, color, + autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL); + + } + + void DrawAmmoNades(vector myPos, vector mySize, float draw_expanding, float expand_time) + { + float theAlpha = 1, a, b; + vector nade_color, picpos, numpos; + + nade_color = Nade_Color(getstati(STAT_NADE_BONUS_TYPE)); + + a = getstatf(STAT_NADE_BONUS); + b = getstatf(STAT_NADE_BONUS_SCORE); + + if(autocvar_hud_panel_ammo_iconalign) + { + numpos = myPos; + picpos = myPos + eX * 2 * mySize_y; + } + else + { + numpos = myPos + eX * mySize_y; + picpos = myPos; + } + + DrawNadeScoreBar(myPos, mySize, nade_color); + + if(b > 0 || a > 0) + { + if(autocvar_hud_panel_ammo_text) + drawstring_aspect(numpos, ftos(a), eX * (2/3) * mySize_x + eY * mySize_y, '1 1 1', panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL); + + if(draw_expanding) + drawpic_aspect_skin_expanding(picpos, "nade_nbg", '1 1 0' * mySize_y, '1 1 1', panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL, expand_time); + + drawpic_aspect_skin(picpos, "nade_bg" , '1 1 0' * mySize_y, '1 1 1', panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL); + drawpic_aspect_skin(picpos, "nade_nbg" , '1 1 0' * mySize_y, nade_color, panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL); + } + } + -void DrawAmmoItem(vector myPos, vector mySize, float itemcode, float currently_selected, float infinite_ammo) +void DrawAmmoItem(vector myPos, vector mySize, .float ammotype, float currently_selected, float infinite_ammo) { - float a; - if(autocvar__hud_configure) + float a = 0; + if(ammotype != ammo_none) { - currently_selected = (itemcode == 2); //rockets always selected - a = 31 + mod(itemcode*93, 128); + if(autocvar__hud_configure) + { + currently_selected = (ammotype == ammo_rockets); //rockets always selected + a = 60; + } + else + { + // how much ammo do we have of this ammotype? + a = getstati(GetAmmoStat(ammotype)); + } } else - a = getstati(GetAmmoStat(itemcode)); // how much ammo do we have of type itemcode? + { + #if 0 + infinite_ammo = TRUE; + #else + return; // just don't draw infinite ammo at all. + #endif + } vector color; if(infinite_ammo) @@@ -896,11 -985,14 +944,14 @@@ drawstring_aspect(numpos, ftos(a), eX * (2/3) * mySize_x + eY * mySize_y, '0 0 0', panel_fg_alpha * theAlpha * 0.5, DRAWFLAG_NORMAL); } if(a > 0 || infinite_ammo) - drawpic_aspect_skin(picpos, GetAmmoPicture(itemcode), '1 1 0' * mySize_y, '1 1 1', panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL); + drawpic_aspect_skin(picpos, GetAmmoPicture(ammotype), '1 1 0' * mySize_y, '1 1 1', panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL); else // "ghost" ammo icon - drawpic_aspect_skin(picpos, GetAmmoPicture(itemcode), '1 1 0' * mySize_y, '0 0 0', panel_fg_alpha * theAlpha * 0.5, DRAWFLAG_NORMAL); + drawpic_aspect_skin(picpos, GetAmmoPicture(ammotype), '1 1 0' * mySize_y, '0 0 0', panel_fg_alpha * theAlpha * 0.5, DRAWFLAG_NORMAL); } + float nade_prevstatus; + float nade_prevframe; + float nade_statuschange_time; void HUD_Ammo(void) { if(hud != HUD_NORMAL) return; @@@ -925,21 -1017,39 +976,38 @@@ mySize -= '2 2 0' * panel_bg_padding; } - const float AMMO_COUNT = 5; float rows = 0, columns, row, column; + float nade_cnt = getstatf(STAT_NADE_BONUS), nade_score = getstatf(STAT_NADE_BONUS_SCORE); + float draw_nades = (nade_cnt > 0 || nade_score > 0), nade_statuschange_elapsedtime; + float total_ammo_count; + vector ammo_size; - if(autocvar_hud_panel_ammo_onlycurrent) - ammo_size = mySize; - float AMMO_COUNT = 4; + if (autocvar_hud_panel_ammo_onlycurrent) + total_ammo_count = 1; else + total_ammo_count = AMMO_COUNT - 1; // fuel + + if(draw_nades) { - rows = mySize_y/mySize_x; - rows = bound(1, floor((sqrt(4 * (3/1) * rows * AMMO_COUNT + rows * rows) + rows + 0.5) / 2), AMMO_COUNT); - // ^^^ ammo item aspect goes here + ++total_ammo_count; + if (nade_cnt != nade_prevframe) + { + nade_statuschange_time = time; + nade_prevstatus = nade_prevframe; + nade_prevframe = nade_cnt; + } + } + else + nade_prevstatus = nade_prevframe = nade_statuschange_time = 0; - columns = ceil(AMMO_COUNT/rows); + rows = mySize_y/mySize_x; + rows = bound(1, floor((sqrt(4 * (3/1) * rows * (total_ammo_count) + rows * rows) + rows + 0.5) / 2), (total_ammo_count)); + // ^^^ ammo item aspect goes here - ammo_size = eX * mySize_x*(1/columns) + eY * mySize_y*(1/rows); - } + columns = ceil((total_ammo_count)/rows); + + ammo_size = eX * mySize_x*(1/columns) + eY * mySize_y*(1/rows); + local vector offset = '0 0 0'; // fteqcc sucks float newSize; @@@ -958,9 -1068,12 +1026,10 @@@ ammo_size_y = newSize; } - float i, stat_items, currently_selected, infinite_ammo; - infinite_ammo = FALSE; - + float i; + float infinite_ammo = (getstati(STAT_ITEMS, 0, 24) & IT_UNLIMITED_WEAPON_AMMO); + row = column = 0; - - if (autocvar_hud_panel_ammo_onlycurrent) + if(autocvar_hud_panel_ammo_onlycurrent) { if(autocvar__hud_configure) { @@@ -968,14 -1081,25 +1037,21 @@@ } else { - stat_items = getstati(STAT_ITEMS, 0, 24); - if (stat_items & IT_UNLIMITED_WEAPON_AMMO) - infinite_ammo = TRUE; - for (i = 0; i < AMMO_COUNT; ++i) { - currently_selected = stat_items & GetAmmoItemCode(i); - if (currently_selected) - { - DrawAmmoItem(pos, ammo_size, i, true, infinite_ammo); - break; - } - } - } + DrawAmmoItem( + pos, + ammo_size, + (get_weaponinfo(switchweapon)).ammo_field, + TRUE, + infinite_ammo + ); + + ++row; + if(row >= rows) + { + row = 0; + column = column + 1; + } + } } else { diff --cc qcsrc/client/progs.src index 36c20b658,86a8b4339..90f445c51 --- a/qcsrc/client/progs.src +++ b/qcsrc/client/progs.src @@@ -16,9 -17,12 +17,11 @@@ Defs.q ../common/teams.qh ../common/util.qh + ../common/nades.qh + ../common/buffs.qh ../common/test.qh ../common/counting.qh -../common/items.qh -../common/explosion_equation.qh +../common/weapons/weapons.qh // TODO ../common/mapinfo.qh ../common/command/markup.qh ../common/command/rpn.qh diff --cc qcsrc/client/waypointsprites.qc index 24b200d34,7ee34672c..c99fc5d51 --- a/qcsrc/client/waypointsprites.qc +++ b/qcsrc/client/waypointsprites.qc @@@ -228,9 -241,7 +228,9 @@@ vector spritelookupcolor(string s, vect } string spritelookuptext(string s) { - if(substring(s, 0, 4) == "wpn-") - return (get_weaponinfo(stof(substring(s, 4, strlen(s)))).message); ++ if(substring(s, 0, 4) == "wpn-") { return (get_weaponinfo(stof(substring(s, 4, strlen(s)))).message); } + if(substring(s, 0, 5) == "buff-") { return Buff_PrettyName(Buff_Type_FromSprite(s)); } + switch(s) { case "as-push": return _("Push"); diff --cc qcsrc/client/weapons/projectile.qc index c98ab7bf4,000000000..58bd11ee4 mode 100644,000000..100644 --- a/qcsrc/client/weapons/projectile.qc +++ b/qcsrc/client/weapons/projectile.qc @@@ -1,538 -1,0 +1,505 @@@ +.vector iorigin1, iorigin2; +.float spawntime; +.vector trail_oldorigin; +.float trail_oldtime; +.float fade_time, fade_rate; + +void SUB_Stop() +{ + self.move_velocity = self.move_avelocity = '0 0 0'; + self.move_movetype = MOVETYPE_NONE; +} + +.float alphamod; +.float count; // set if clientside projectile +.float cnt; // sound index +.float gravity; +.float snd_looping; +.float silent; + +void Projectile_ResetTrail(vector to) +{ + self.trail_oldorigin = to; + self.trail_oldtime = time; +} + +void Projectile_DrawTrail(vector to) +{ + vector from; + float t0; + + from = self.trail_oldorigin; + t0 = self.trail_oldtime; + self.trail_oldorigin = to; + self.trail_oldtime = time; + + // force the effect even for stationary firemine + if(self.cnt == PROJECTILE_FIREMINE) + if(from == to) + from_z += 1; + + if (self.traileffect) + { + particles_alphamin = particles_alphamax = particles_fade = sqrt(self.alpha); + boxparticles(self.traileffect, self, from, to, self.velocity, self.velocity, 1, PARTICLES_USEALPHA | PARTICLES_USEFADE | PARTICLES_DRAWASTRAIL); + } +} + +void Projectile_Draw() +{ + vector rot; + vector trailorigin; + float f; + float drawn; + float t; + float a; + + f = self.move_flags; + + if(self.count & 0x80) + { + //self.move_flags &= ~FL_ONGROUND; + if(self.move_movetype == MOVETYPE_NONE || self.move_movetype == MOVETYPE_FLY) + Movetype_Physics_NoMatchServer(); + // the trivial movetypes do not have to match the + // server's ticrate as they are ticrate independent + // NOTE: this assumption is only true if MOVETYPE_FLY + // projectiles detonate on impact. If they continue + // moving, we might still be ticrate dependent. + else + Movetype_Physics_MatchServer(autocvar_cl_projectiles_sloppy); + if(!(self.move_flags & FL_ONGROUND)) + if(self.velocity != '0 0 0') + self.move_angles = self.angles = vectoangles(self.velocity); + } + else + { + InterpolateOrigin_Do(); + } + + if(self.count & 0x80) + { + drawn = (time >= self.spawntime - 0.02); + t = max(time, self.spawntime); + } + else + { + drawn = (self.iflags & IFLAG_VALID); + t = time; + } + + if(!(f & FL_ONGROUND)) + { + rot = '0 0 0'; + switch(self.cnt) + { + /* + case PROJECTILE_GRENADE: + rot = '-2000 0 0'; // forward + break; + */ + case PROJECTILE_GRENADE_BOUNCING: + rot = '0 -1000 0'; // sideways + break; - case PROJECTILE_NADE_RED_BURN: - case PROJECTILE_NADE_RED: - case PROJECTILE_NADE_BLUE_BURN: - case PROJECTILE_NADE_BLUE: - case PROJECTILE_NADE_YELLOW_BURN: - case PROJECTILE_NADE_YELLOW: - case PROJECTILE_NADE_PINK_BURN: - case PROJECTILE_NADE_PINK: - case PROJECTILE_NADE_BURN: - case PROJECTILE_NADE: - rot = self.avelocity; - break; + case PROJECTILE_HOOKBOMB: + rot = '1000 0 0'; // forward + break; + default: + break; + } ++ ++ if(Nade_IDFromProjectile(self.cnt) != 0) ++ rot = self.avelocity; ++ + self.angles = AnglesTransform_ToAngles(AnglesTransform_Multiply(AnglesTransform_FromAngles(self.angles), rot * (t - self.spawntime))); + } + + vector ang; + ang = self.angles; + ang_x = -ang_x; + makevectors(ang); + + a = 1 - (time - self.fade_time) * self.fade_rate; + self.alpha = bound(0, self.alphamod * a, 1); + if(self.alpha <= 0) + drawn = 0; + self.renderflags = 0; + + trailorigin = self.origin; + switch(self.cnt) + { - case PROJECTILE_NADE_RED_BURN: - case PROJECTILE_NADE_RED: - case PROJECTILE_NADE_BLUE_BURN: - case PROJECTILE_NADE_BLUE: - case PROJECTILE_NADE_YELLOW_BURN: - case PROJECTILE_NADE_YELLOW: - case PROJECTILE_NADE_PINK_BURN: - case PROJECTILE_NADE_PINK: - case PROJECTILE_NADE_BURN: - case PROJECTILE_NADE: - trailorigin += v_up * 4; - break; + case PROJECTILE_GRENADE: + case PROJECTILE_GRENADE_BOUNCING: + trailorigin += v_right * 1 + v_forward * -10; + break; + default: + break; + } ++ ++ if(Nade_IDFromProjectile(self.cnt) != 0) ++ trailorigin += v_up * 4; ++ + if(drawn) + Projectile_DrawTrail(trailorigin); + else + Projectile_ResetTrail(trailorigin); + + self.drawmask = 0; + + if(!drawn) + return; + + switch(self.cnt) + { + // Possibly add dlights here. + default: + break; + } + + self.drawmask = MASK_NORMAL; +} + +void loopsound(entity e, float ch, string samp, float vol, float attn) +{ + if(self.silent) + return; + + sound(e, ch, samp, vol, attn); + e.snd_looping = ch; +} + +void Ent_RemoveProjectile() +{ + if(self.count & 0x80) + { + tracebox(self.origin, self.mins, self.maxs, self.origin + self.velocity * 0.05, MOVE_NORMAL, self); + Projectile_DrawTrail(trace_endpos); + } +} + +void Ent_Projectile() +{ + float f; + + // projectile properties: + // kind (interpolated, or clientside) + // + // modelindex + // origin + // scale + // if clientside: + // velocity + // gravity + // soundindex (hardcoded list) + // effects + // + // projectiles don't send angles, because they always follow the velocity + + f = ReadByte(); + self.count = (f & 0x80); + self.iflags = (self.iflags & IFLAG_INTERNALMASK) | IFLAG_AUTOANGLES | IFLAG_ANGLES | IFLAG_ORIGIN; + self.solid = SOLID_TRIGGER; + //self.effects = EF_NOMODELFLAGS; + + // this should make collisions with bmodels more exact, but it leads to + // projectiles no longer being able to lie on a bmodel + self.move_nomonsters = MOVE_WORLDONLY; + if(f & 0x40) + self.move_flags |= FL_ONGROUND; + else + self.move_flags &= ~FL_ONGROUND; + + if(!self.move_time) + { + // for some unknown reason, we don't need to care for + // sv_gameplayfix_delayprojectiles here. + self.move_time = time; + self.spawntime = time; + } + else + self.move_time = max(self.move_time, time); + + if(!(self.count & 0x80)) + InterpolateOrigin_Undo(); + + if(f & 1) + { + self.origin_x = ReadCoord(); + self.origin_y = ReadCoord(); + self.origin_z = ReadCoord(); + setorigin(self, self.origin); + if(self.count & 0x80) + { + self.velocity_x = ReadCoord(); + self.velocity_y = ReadCoord(); + self.velocity_z = ReadCoord(); + if(f & 0x10) + self.gravity = ReadCoord(); + else + self.gravity = 0; // none + self.move_origin = self.origin; + self.move_velocity = self.velocity; + } + + if(time == self.spawntime || (self.count & 0x80) || (f & 0x08)) + { + self.trail_oldorigin = self.origin; + if(!(self.count & 0x80)) + InterpolateOrigin_Reset(); + } + + if(f & 0x20) + { + self.fade_time = time + ReadByte() * ticrate; + self.fade_rate = 1 / (ReadByte() * ticrate); + } + else + { + self.fade_time = 0; + self.fade_rate = 0; + } ++ ++ self.team = ReadByte() - 1; + } + + if(f & 2) + { + self.cnt = ReadByte(); + + self.silent = (self.cnt & 0x80); + self.cnt = (self.cnt & 0x7F); + + self.scale = 1; + self.traileffect = 0; + switch(self.cnt) + { + case PROJECTILE_ELECTRO: setmodel(self, "models/ebomb.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break; + case PROJECTILE_ROCKET: setmodel(self, "models/rocket.md3");self.traileffect = particleeffectnum("TR_ROCKET"); self.scale = 2; break; + case PROJECTILE_CRYLINK: setmodel(self, "models/plasmatrail.mdl");self.traileffect = particleeffectnum("TR_CRYLINKPLASMA"); break; + case PROJECTILE_CRYLINK_BOUNCING: setmodel(self, "models/plasmatrail.mdl");self.traileffect = particleeffectnum("TR_CRYLINKPLASMA"); break; + case PROJECTILE_ELECTRO_BEAM: setmodel(self, "models/elaser.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break; + case PROJECTILE_GRENADE: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_GRENADE"); break; + case PROJECTILE_GRENADE_BOUNCING: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_GRENADE"); break; + case PROJECTILE_MINE: setmodel(self, "models/mine.md3");self.traileffect = particleeffectnum("TR_GRENADE"); break; + case PROJECTILE_BLASTER: setmodel(self, "models/laser.mdl");self.traileffect = particleeffectnum(""); break; + case PROJECTILE_HLAC: setmodel(self, "models/hlac_bullet.md3");self.traileffect = particleeffectnum(""); break; + case PROJECTILE_PORTO_RED: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_WIZSPIKE"); self.scale = 4; break; + case PROJECTILE_PORTO_BLUE: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_WIZSPIKE"); self.scale = 4; break; + case PROJECTILE_HOOKBOMB: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_KNIGHTSPIKE"); break; + case PROJECTILE_HAGAR: setmodel(self, "models/hagarmissile.mdl");self.traileffect = particleeffectnum("tr_hagar"); self.scale = 0.75; break; + case PROJECTILE_HAGAR_BOUNCING: setmodel(self, "models/hagarmissile.mdl");self.traileffect = particleeffectnum("tr_hagar"); self.scale = 0.75; break; ++ case PROJECTILE_NAPALM_FOUNTAIN: //self.model = ""; self.modelindex = 0; self.traileffect = particleeffectnum("torch_small"); break; + case PROJECTILE_FIREBALL: self.model = ""; self.modelindex = 0; self.traileffect = particleeffectnum("fireball"); break; // particle effect is good enough + case PROJECTILE_FIREMINE: self.model = ""; self.modelindex = 0; self.traileffect = particleeffectnum("firemine"); break; // particle effect is good enough + case PROJECTILE_TAG: setmodel(self, "models/laser.mdl"); self.traileffect = particleeffectnum("TR_ROCKET"); break; + case PROJECTILE_FLAC: setmodel(self, "models/hagarmissile.mdl"); self.scale = 0.4; self.traileffect = particleeffectnum("TR_SEEKER"); break; + case PROJECTILE_SEEKER: setmodel(self, "models/tagrocket.md3"); self.traileffect = particleeffectnum("TR_SEEKER"); break; + + case PROJECTILE_MAGE_SPIKE: setmodel(self, "models/ebomb.mdl"); self.traileffect = particleeffectnum("TR_VORESPIKE"); break; + case PROJECTILE_SHAMBLER_LIGHTNING: setmodel(self, "models/ebomb.mdl"); self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break; + + case PROJECTILE_RAPTORBOMB: setmodel(self, "models/vehicles/clusterbomb.md3"); self.gravity = 1; self.avelocity = '0 0 180'; self.traileffect = particleeffectnum(""); break; + case PROJECTILE_RAPTORBOMBLET: setmodel(self, "models/vehicles/bomblet.md3"); self.gravity = 1; self.avelocity = '0 0 180'; self.traileffect = particleeffectnum(""); break; + case PROJECTILE_RAPTORCANNON: setmodel(self, "models/plasmatrail.mdl"); self.traileffect = particleeffectnum("TR_CRYLINKPLASMA"); break; + + case PROJECTILE_SPIDERROCKET: setmodel(self, "models/vehicles/rocket02.md3"); self.traileffect = particleeffectnum("spiderbot_rocket_thrust"); break; + case PROJECTILE_WAKIROCKET: setmodel(self, "models/vehicles/rocket01.md3"); self.traileffect = particleeffectnum("wakizashi_rocket_thrust"); break; + case PROJECTILE_WAKICANNON: setmodel(self, "models/laser.mdl"); self.traileffect = particleeffectnum(""); break; + + case PROJECTILE_BUMBLE_GUN: setmodel(self, "models/elaser.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break; + case PROJECTILE_BUMBLE_BEAM: setmodel(self, "models/elaser.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break; + - case PROJECTILE_NADE_RED: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_red"); break; - case PROJECTILE_NADE_RED_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_red_burn"); break; - case PROJECTILE_NADE_BLUE: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_blue"); break; - case PROJECTILE_NADE_BLUE_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_blue_burn"); break; - case PROJECTILE_NADE_YELLOW: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_yellow"); break; - case PROJECTILE_NADE_YELLOW_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_yellow_burn"); break; - case PROJECTILE_NADE_PINK: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_pink"); break; - case PROJECTILE_NADE_PINK_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_pink_burn"); break; - case PROJECTILE_NADE: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade"); break; - case PROJECTILE_NADE_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_burn"); break; - + default: ++ if(Nade_IDFromProjectile(self.cnt) != 0) { setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum(Nade_TrailEffect(self.cnt, self.team)); break; } + error("Received invalid CSQC projectile, can't work with this!"); + break; + } + + self.mins = '0 0 0'; + self.maxs = '0 0 0'; + self.colormod = '0 0 0'; + self.move_touch = SUB_Stop; + self.move_movetype = MOVETYPE_TOSS; + self.alphamod = 1; + + switch(self.cnt) + { + case PROJECTILE_ELECTRO: + // only new engines support sound moving with object + loopsound(self, CH_SHOTS_SINGLE, "weapons/electro_fly.wav", VOL_BASE, ATTEN_NORM); + self.mins = '0 0 -4'; + self.maxs = '0 0 -4'; + self.move_movetype = MOVETYPE_BOUNCE; + self.move_touch = func_null; + self.move_bounce_factor = g_balance_electro_secondary_bouncefactor; + self.move_bounce_stopspeed = g_balance_electro_secondary_bouncestop; + break; + case PROJECTILE_ROCKET: + loopsound(self, CH_SHOTS_SINGLE, "weapons/rocket_fly.wav", VOL_BASE, ATTEN_NORM); + self.mins = '-3 -3 -3'; + self.maxs = '3 3 3'; + break; + case PROJECTILE_GRENADE: + self.mins = '-3 -3 -3'; + self.maxs = '3 3 3'; + break; - case PROJECTILE_NADE_RED_BURN: - case PROJECTILE_NADE_RED: - case PROJECTILE_NADE_BLUE_BURN: - case PROJECTILE_NADE_BLUE: - self.mins = '-3 -3 -3'; - self.maxs = '3 3 3'; - self.move_movetype = MOVETYPE_BOUNCE; - self.move_touch = func_null; - self.scale = 1.5; - self.avelocity = randomvec() * 720; - break; + case PROJECTILE_GRENADE_BOUNCING: + self.mins = '-3 -3 -3'; + self.maxs = '3 3 3'; + self.move_movetype = MOVETYPE_BOUNCE; + self.move_touch = func_null; + self.move_bounce_factor = g_balance_grenadelauncher_bouncefactor; + self.move_bounce_stopspeed = g_balance_grenadelauncher_bouncestop; + break; - case PROJECTILE_NADE_RED_BURN: - case PROJECTILE_NADE_RED: - case PROJECTILE_NADE_BLUE_BURN: - case PROJECTILE_NADE_BLUE: - case PROJECTILE_NADE_YELLOW_BURN: - case PROJECTILE_NADE_YELLOW: - case PROJECTILE_NADE_PINK_BURN: - case PROJECTILE_NADE_PINK: - case PROJECTILE_NADE_BURN: - case PROJECTILE_NADE: - self.mins = '-16 -16 -16'; - self.maxs = '16 16 16'; - self.move_movetype = MOVETYPE_BOUNCE; - self.move_touch = func_null; - self.scale = 1.5; - self.avelocity = randomvec() * 720; - break; + case PROJECTILE_SHAMBLER_LIGHTNING: + self.mins = '-8 -8 -8'; + self.maxs = '8 8 8'; + self.scale = 2.5; + self.avelocity = randomvec() * 720; + break; + case PROJECTILE_MINE: + self.mins = '-4 -4 -4'; + self.maxs = '4 4 4'; + break; + case PROJECTILE_PORTO_RED: + self.colormod = '2 1 1'; + self.alphamod = 0.5; + self.move_movetype = MOVETYPE_BOUNCE; + self.move_touch = func_null; + break; + case PROJECTILE_PORTO_BLUE: + self.colormod = '1 1 2'; + self.alphamod = 0.5; + self.move_movetype = MOVETYPE_BOUNCE; + self.move_touch = func_null; + break; + case PROJECTILE_HAGAR_BOUNCING: + self.move_movetype = MOVETYPE_BOUNCE; + self.move_touch = func_null; + break; + case PROJECTILE_CRYLINK_BOUNCING: + self.move_movetype = MOVETYPE_BOUNCE; + self.move_touch = func_null; + break; ++ case PROJECTILE_NAPALM_FOUNTAIN: + case PROJECTILE_FIREBALL: + loopsound(self, CH_SHOTS_SINGLE, "weapons/fireball_fly2.wav", VOL_BASE, ATTEN_NORM); + self.mins = '-16 -16 -16'; + self.maxs = '16 16 16'; + break; + case PROJECTILE_FIREMINE: + loopsound(self, CH_SHOTS_SINGLE, "weapons/fireball_fly.wav", VOL_BASE, ATTEN_NORM); + self.move_movetype = MOVETYPE_BOUNCE; + self.move_touch = func_null; + self.mins = '-4 -4 -4'; + self.maxs = '4 4 4'; + break; + case PROJECTILE_TAG: + self.mins = '-2 -2 -2'; + self.maxs = '2 2 2'; + break; + case PROJECTILE_FLAC: + self.mins = '-2 -2 -2'; + self.maxs = '2 2 2'; + break; + case PROJECTILE_SEEKER: + loopsound(self, CH_SHOTS_SINGLE, "weapons/tag_rocket_fly.wav", VOL_BASE, ATTEN_NORM); + self.mins = '-4 -4 -4'; + self.maxs = '4 4 4'; + break; + case PROJECTILE_RAPTORBOMB: + self.mins = '-3 -3 -3'; + self.maxs = '3 3 3'; + break; + case PROJECTILE_RAPTORBOMBLET: + break; + case PROJECTILE_RAPTORCANNON: + break; + case PROJECTILE_SPIDERROCKET: + loopsound(self, CH_SHOTS_SINGLE, "weapons/tag_rocket_fly.wav", VOL_BASE, ATTEN_NORM); + break; + case PROJECTILE_WAKIROCKET: + loopsound(self, CH_SHOTS_SINGLE, "weapons/tag_rocket_fly.wav", VOL_BASE, ATTEN_NORM); + break; + /* + case PROJECTILE_WAKICANNON: + break; + case PROJECTILE_BUMBLE_GUN: + // only new engines support sound moving with object + loopsound(self, CH_SHOTS_SINGLE, "weapons/electro_fly.wav", VOL_BASE, ATTEN_NORM); + self.mins = '0 0 -4'; + self.maxs = '0 0 -4'; + self.move_movetype = MOVETYPE_BOUNCE; + self.move_touch = func_null; + self.move_bounce_factor = g_balance_electro_secondary_bouncefactor; + self.move_bounce_stopspeed = g_balance_electro_secondary_bouncestop; + break; + */ + default: + break; + } ++ ++ if(Nade_IDFromProjectile(self.cnt) != 0) ++ { ++ self.mins = '-16 -16 -16'; ++ self.maxs = '16 16 16'; ++ self.colormod = Nade_Color(Nade_IDFromProjectile(self.cnt)); ++ self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; ++ self.move_movetype = MOVETYPE_BOUNCE; ++ self.move_touch = func_null; ++ self.scale = 1.5; ++ self.avelocity = randomvec() * 720; ++ ++ if(Nade_IDFromProjectile(self.cnt) == NADE_TYPE_TRANSLOCATE) ++ self.solid = SOLID_TRIGGER; ++ } ++ + setsize(self, self.mins, self.maxs); + } + + if(self.gravity) + { + if(self.move_movetype == MOVETYPE_FLY) + self.move_movetype = MOVETYPE_TOSS; + if(self.move_movetype == MOVETYPE_BOUNCEMISSILE) + self.move_movetype = MOVETYPE_BOUNCE; + } + else + { + if(self.move_movetype == MOVETYPE_TOSS) + self.move_movetype = MOVETYPE_FLY; + if(self.move_movetype == MOVETYPE_BOUNCE) + self.move_movetype = MOVETYPE_BOUNCEMISSILE; + } + + if(!(self.count & 0x80)) + InterpolateOrigin_Note(); + + self.draw = Projectile_Draw; + self.entremove = Ent_RemoveProjectile; +} + +void Projectile_Precache() +{ + precache_model("models/ebomb.mdl"); + precache_model("models/elaser.mdl"); + precache_model("models/grenademodel.md3"); + precache_model("models/mine.md3"); + precache_model("models/hagarmissile.mdl"); + precache_model("models/hlac_bullet.md3"); + precache_model("models/laser.mdl"); + precache_model("models/plasmatrail.mdl"); + precache_model("models/rocket.md3"); + precache_model("models/tagrocket.md3"); + precache_model("models/tracer.mdl"); ++ precache_model("models/sphere/sphere.md3"); + + precache_model("models/weapons/v_ok_grenade.md3"); + + precache_sound("weapons/electro_fly.wav"); + precache_sound("weapons/rocket_fly.wav"); + precache_sound("weapons/fireball_fly.wav"); + precache_sound("weapons/fireball_fly2.wav"); + precache_sound("weapons/tag_rocket_fly.wav"); + +} diff --cc qcsrc/common/notifications.qh index 3e2b40528,60cc622fa..27e2f7ede --- a/qcsrc/common/notifications.qh +++ b/qcsrc/common/notifications.qh @@@ -991,7 -1018,8 +1024,8 @@@ string arg_slot[NOTIF_MAX_ARGS] ARG_CASE(ARG_CS_SV, "spree_inf", (autocvar_notification_show_sprees ? notif_arg_spree_inf(1, input, s2, f2) : "")) \ ARG_CASE(ARG_CS_SV, "spree_end", (autocvar_notification_show_sprees ? notif_arg_spree_inf(-1, "", "", f1) : "")) \ ARG_CASE(ARG_CS_SV, "spree_lost", (autocvar_notification_show_sprees ? notif_arg_spree_inf(-2, "", "", f1) : "")) \ - ARG_CASE(ARG_CS_SV, "item_wepname", W_Name(f1)) \ + ARG_CASE(ARG_CS_SV, "item_wepname", WEP_NAME(f1)) \ + ARG_CASE(ARG_CS_SV, "item_buffname", sprintf("%s%s", rgb_to_hexcolor(Buff_Color(f1)), Buff_PrettyName(f1))) \ ARG_CASE(ARG_CS_SV, "item_wepammo", (s1 != "" ? sprintf(_(" with %s"), s1) : "")) \ ARG_CASE(ARG_DC, "item_centime", ftos(autocvar_notification_item_centerprinttime)) \ ARG_CASE(ARG_SV, "death_team", Team_ColoredFullName(f1)) \ diff --cc qcsrc/common/stats.qh index 000000000,793582e12..44a540b65 mode 000000,100644..100644 --- a/qcsrc/common/stats.qh +++ b/qcsrc/common/stats.qh @@@ -1,0 -1,290 +1,290 @@@ + // Full list of all stat constants, icnluded in a single location for easy reference + // 255 is the current limit (MAX_CL_STATS - 1), engine will need to be modified if you wish to add more stats + + const float MAX_CL_STATS = 256; + const float STAT_HEALTH = 0; + // 1 empty? + const float STAT_WEAPON = 2; + const float STAT_AMMO = 3; + const float STAT_ARMOR = 4; + const float STAT_WEAPONFRAME = 5; + const float STAT_SHELLS = 6; + const float STAT_NAILS = 7; + const float STAT_ROCKETS = 8; + const float STAT_CELLS = 9; + const float STAT_ACTIVEWEAPON = 10; + const float STAT_TOTALSECRETS = 11; + const float STAT_TOTALMONSTERS = 12; + const float STAT_SECRETS = 13; + const float STAT_MONSTERS = 14; + const float STAT_ITEMS = 15; + const float STAT_VIEWHEIGHT = 16; + // 17 empty? + // 18 empty? + // 19 empty? + // 20 empty? + const float STAT_VIEWZOOM = 21; + // 22 empty? + // 23 empty? + // 24 empty? + // 25 empty? + // 26 empty? + // 27 empty? + // 28 empty? + // 29 empty? + // 30 empty? + // 31 empty? + const float STAT_KH_KEYS = 32; + const float STAT_CTF_STATE = 33; + // 34 empty? + const float STAT_WEAPONS = 35; + const float STAT_SWITCHWEAPON = 36; + const float STAT_GAMESTARTTIME = 37; + const float STAT_STRENGTH_FINISHED = 38; + const float STAT_INVINCIBLE_FINISHED = 39; + // 40 empty? + // 41 empty? + const float STAT_PRESSED_KEYS = 42; + const float STAT_ALLOW_OLDNEXBEAM = 43; // this stat could later contain some other bits of info, like, more server-side particle config + const float STAT_FUEL = 44; + const float STAT_NB_METERSTART = 45; + const float STAT_SHOTORG = 46; // compressShotOrigin + const float STAT_LEADLIMIT = 47; + const float STAT_WEAPON_CLIPLOAD = 48; + const float STAT_WEAPON_CLIPSIZE = 49; -const float STAT_NEX_CHARGE = 50; ++const float STAT_VORTEX_CHARGE = 50; + const float STAT_LAST_PICKUP = 51; + const float STAT_HUD = 52; -const float STAT_NEX_CHARGEPOOL = 53; ++const float STAT_VORTEX_CHARGEPOOL = 53; + const float STAT_HIT_TIME = 54; + const float STAT_TYPEHIT_TIME = 55; + const float STAT_LAYED_MINES = 56; + const float STAT_HAGAR_LOAD = 57; + const float STAT_SWITCHINGWEAPON = 58; + const float STAT_SUPERWEAPONS_FINISHED = 59; + const float STAT_VEHICLESTAT_HEALTH = 60; + const float STAT_VEHICLESTAT_SHIELD = 61; + const float STAT_VEHICLESTAT_ENERGY = 62; + const float STAT_VEHICLESTAT_AMMO1 = 63; + const float STAT_VEHICLESTAT_RELOAD1 = 64; + const float STAT_VEHICLESTAT_AMMO2 = 65; + const float STAT_VEHICLESTAT_RELOAD2 = 66; + const float STAT_VEHICLESTAT_W2MODE = 67; + // 68 empty? + const float STAT_NADE_TIMER = 69; + const float STAT_SECRETS_TOTAL = 70; + const float STAT_SECRETS_FOUND = 71; + const float STAT_RESPAWN_TIME = 72; + const float STAT_ROUNDSTARTTIME = 73; + const float STAT_WEAPONS2 = 74; + const float STAT_WEAPONS3 = 75; + const float STAT_MONSTERS_TOTAL = 76; + const float STAT_MONSTERS_KILLED = 77; + const float STAT_BUFFS = 78; + const float STAT_NADE_BONUS = 79; + const float STAT_NADE_BONUS_TYPE = 80; + const float STAT_NADE_BONUS_SCORE = 81; + const float STAT_HEALING_ORB = 82; + const float STAT_HEALING_ORB_ALPHA = 83; -// 84 empty? ++const float STAT_PLASMA = 84; + // 85 empty? + // 86 empty? + // 87 empty? + // 88 empty? + // 89 empty? + // 90 empty? + // 91 empty? + // 92 empty? + // 93 empty? + // 94 empty? + // 95 empty? + // 96 empty? + // 97 empty? + // 98 empty? + // 99 empty? + + + /* The following stats change depending on the gamemode, so can share the same ID */ + // IDs 100 to 104 reserved for gamemodes + + // freeze tag, clan arena, jailbreak + const float STAT_REDALIVE = 100; + const float STAT_BLUEALIVE = 101; + const float STAT_YELLOWALIVE = 102; + const float STAT_PINKALIVE = 103; + + // domination + const float STAT_DOM_TOTAL_PPS = 100; + const float STAT_DOM_PPS_RED = 101; + const float STAT_DOM_PPS_BLUE = 102; + const float STAT_DOM_PPS_YELLOW = 103; + const float STAT_DOM_PPS_PINK = 104; + + // vip + const float STAT_VIP = 100; + const float STAT_VIP_RED = 101; + const float STAT_VIP_BLUE = 102; + const float STAT_VIP_YELLOW = 103; + const float STAT_VIP_PINK = 104; + + // key hunt + const float STAT_KH_REDKEY_TEAM = 100; + const float STAT_KH_BLUEKEY_TEAM = 101; + const float STAT_KH_YELLOWKEY_TEAM = 102; + const float STAT_KH_PINKKEY_TEAM = 103; + + /* Gamemode-specific stats end here */ + + + const float STAT_FROZEN = 105; + const float STAT_REVIVE_PROGRESS = 106; + // 107 empty? + // 108 empty? + // 109 empty? + // 110 empty? + // 111 empty? + // 112 empty? + // 113 empty? + // 114 empty? + // 115 empty? + // 116 empty? + // 117 empty? + // 118 empty? + // 119 empty? + // 120 empty? + // 121 empty? + // 122 empty? + // 123 empty? + // 124 empty? + // 125 empty? + // 126 empty? + // 127 empty? + // 128 empty? + // 129 empty? + // 130 empty? + // 131 empty? + // 132 empty? + // 133 empty? + // 134 empty? + // 135 empty? + // 136 empty? + // 137 empty? + // 138 empty? + // 139 empty? + // 140 empty? + // 141 empty? + // 142 empty? + // 143 empty? + // 144 empty? + // 145 empty? + // 146 empty? + // 147 empty? + // 148 empty? + // 149 empty? + // 150 empty? + // 151 empty? + // 152 empty? + // 153 empty? + // 154 empty? + // 155 empty? + // 156 empty? + // 157 empty? + // 158 empty? + // 159 empty? + // 160 empty? + // 161 empty? + // 162 empty? + // 162 empty? + // 163 empty? + // 164 empty? + // 165 empty? + // 166 empty? + // 167 empty? + // 168 empty? + // 169 empty? + // 170 empty? + // 171 empty? + // 172 empty? + // 173 empty? + // 174 empty? + // 175 empty? + // 176 empty? + // 177 empty? + // 178 empty? + // 179 empty? + // 180 empty? + // 181 empty? + // 182 empty? + // 183 empty? + // 184 empty? + // 185 empty? + // 186 empty? + // 187 empty? + // 188 empty? + // 189 empty? + // 190 empty? + // 191 empty? + // 192 empty? + // 193 empty? + // 194 empty? + // 195 empty? + // 196 empty? + // 197 empty? + // 198 empty? + // 199 empty? + // 200 empty? + // 201 empty? + // 202 empty? + // 203 empty? + // 204 empty? + // 205 empty? + // 206 empty? + // 207 empty? + // 208 empty? + // 209 empty? + // 210 empty? + // 211 empty? + // 212 empty? + // 213 empty? + // 214 empty? + // 215 empty? + // 216 empty? + // 217 empty? + // 218 empty? + // 219 empty? + const float STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR = 220; + const float STAT_MOVEVARS_AIRCONTROL_PENALTY = 221; + const float STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW = 222; + const float STAT_MOVEVARS_AIRSTRAFEACCEL_QW = 223; + const float STAT_MOVEVARS_AIRCONTROL_POWER = 224; + const float STAT_MOVEFLAGS = 225; + const float STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL = 226; + const float STAT_MOVEVARS_WARSOWBUNNY_ACCEL = 227; + const float STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED = 228; + const float STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL = 229; + const float STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO = 230; + const float STAT_MOVEVARS_AIRSTOPACCELERATE = 231; + const float STAT_MOVEVARS_AIRSTRAFEACCELERATE = 232; + const float STAT_MOVEVARS_MAXAIRSTRAFESPEED = 233; + const float STAT_MOVEVARS_AIRCONTROL = 234; + const float STAT_FRAGLIMIT = 235; + const float STAT_TIMELIMIT = 236; + const float STAT_MOVEVARS_WALLFRICTION = 237; + const float STAT_MOVEVARS_FRICTION = 238; + const float STAT_MOVEVARS_WATERFRICTION = 239; + const float STAT_MOVEVARS_TICRATE = 240; + const float STAT_MOVEVARS_TIMESCALE = 241; + const float STAT_MOVEVARS_GRAVITY = 242; + const float STAT_MOVEVARS_STOPSPEED = 243; + const float STAT_MOVEVARS_MAXSPEED = 244; + const float STAT_MOVEVARS_SPECTATORMAXSPEED = 245; + const float STAT_MOVEVARS_ACCELERATE = 246; + const float STAT_MOVEVARS_AIRACCELERATE = 247; + const float STAT_MOVEVARS_WATERACCELERATE = 248; + const float STAT_MOVEVARS_ENTGRAVITY = 249; + const float STAT_MOVEVARS_JUMPVELOCITY = 250; + const float STAT_MOVEVARS_EDGEFRICTION = 251; + const float STAT_MOVEVARS_MAXAIRSPEED = 252; + const float STAT_MOVEVARS_STEPHEIGHT = 253; + const float STAT_MOVEVARS_AIRACCEL_QW = 254; + const float STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION = 255; diff --cc qcsrc/common/weapons/w_arc.qc index e01d30be9,000000000..c666619e5 mode 100644,000000..100644 --- a/qcsrc/common/weapons/w_arc.qc +++ b/qcsrc/common/weapons/w_arc.qc @@@ -1,1427 -1,0 +1,1427 @@@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ ARC, +/* function */ W_Arc, +/* ammotype */ ammo_cells, +/* impulse */ 3, +/* flags */ WEP_FLAG_NORMAL, +/* rating */ BOT_PICKUP_RATING_HIGH, +/* color */ '1 1 1', +/* modelname */ "arc", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairhlac 0.7", +/* wepimg */ "weaponhlac", +/* refname */ "arc", +/* wepname */ _("Arc") +); + +#define ARC_SETTINGS(w_cvar,w_prop) ARC_SETTINGS_LIST(w_cvar, w_prop, ARC, arc) +#define ARC_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, NONE, beam_ammo) \ + w_cvar(id, sn, NONE, beam_animtime) \ + w_cvar(id, sn, NONE, beam_botaimspeed) \ + w_cvar(id, sn, NONE, beam_botaimlifetime) \ + w_cvar(id, sn, NONE, beam_damage) \ + w_cvar(id, sn, NONE, beam_degreespersegment) \ + w_cvar(id, sn, NONE, beam_distancepersegment) \ + w_cvar(id, sn, NONE, beam_falloff_halflifedist) \ + w_cvar(id, sn, NONE, beam_falloff_maxdist) \ + w_cvar(id, sn, NONE, beam_falloff_mindist) \ + w_cvar(id, sn, NONE, beam_force) \ + w_cvar(id, sn, NONE, beam_healing_amax) \ + w_cvar(id, sn, NONE, beam_healing_aps) \ + w_cvar(id, sn, NONE, beam_healing_hmax) \ + w_cvar(id, sn, NONE, beam_healing_hps) \ + w_cvar(id, sn, NONE, beam_maxangle) \ + w_cvar(id, sn, NONE, beam_nonplayerdamage) \ + w_cvar(id, sn, NONE, beam_range) \ + w_cvar(id, sn, NONE, beam_refire) \ + w_cvar(id, sn, NONE, beam_returnspeed) \ + w_cvar(id, sn, NONE, beam_tightness) \ + w_cvar(id, sn, NONE, burst_ammo) \ + w_cvar(id, sn, NONE, burst_damage) \ + w_cvar(id, sn, NONE, burst_healing_aps) \ + w_cvar(id, sn, NONE, burst_healing_hps) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifndef MENUQC +#define ARC_MAX_SEGMENTS 20 +vector arc_shotorigin[4]; +.vector beam_start; +.vector beam_dir; +.vector beam_wantdir; +.float beam_type; + +#define ARC_BT_MISS 0 +#define ARC_BT_WALL 1 +#define ARC_BT_HEAL 2 +#define ARC_BT_HIT 3 +#define ARC_BT_BURST_MISS 10 +#define ARC_BT_BURST_WALL 11 +#define ARC_BT_BURST_HEAL 12 +#define ARC_BT_BURST_HIT 13 +#define ARC_BT_BURSTMASK 10 + +#define ARC_SF_SETTINGS 1 +#define ARC_SF_START 2 +#define ARC_SF_WANTDIR 4 +#define ARC_SF_BEAMDIR 8 +#define ARC_SF_BEAMTYPE 16 +#define ARC_SF_LOCALMASK 14 +#endif +#ifdef SVQC +ARC_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +.entity arc_beam; +.float BUTTON_ATCK_prev; // for better animation control +.float beam_prev; +.float beam_initialized; +.float beam_bursting; +.float beam_teleporttime; +#endif +#ifdef CSQC +void Ent_ReadArcBeam(float isnew); + +.vector beam_color; +.float beam_alpha; +.float beam_thickness; +.float beam_traileffect; +.float beam_hiteffect; +.float beam_hitlight[4]; // 0: radius, 123: rgb +.float beam_muzzleeffect; +.float beam_muzzlelight[4]; // 0: radius, 123: rgb +.string beam_image; + +.entity beam_muzzleentity; + +.float beam_degreespersegment; +.float beam_distancepersegment; +.float beam_usevieworigin; +.float beam_initialized; +.float beam_maxangle; +.float beam_range; +.float beam_returnspeed; +.float beam_tightness; +.vector beam_shotorigin; + +entity Draw_ArcBeam_callback_entity; +float Draw_ArcBeam_callback_last_thickness; +vector Draw_ArcBeam_callback_last_top; // NOTE: in same coordinate system as player. +vector Draw_ArcBeam_callback_last_bottom; // NOTE: in same coordinate system as player. +#endif +#else +#ifdef SVQC +void spawnfunc_weapon_arc(void) { weapon_defaultspawnfunc(WEP_ARC); } + +float W_Arc_Beam_Send(entity to, float sf) +{ + WriteByte(MSG_ENTITY, ENT_CLIENT_ARC_BEAM); + + // Truncate information when this beam is displayed to the owner client + // - The owner client has no use for beam start position or directions, + // it always figures this information out for itself with csqc code. + // - Spectating the owner also truncates this information. + float drawlocal = ((to == self.owner) || ((to.enemy == self.owner) && IS_SPEC(to))); + if(drawlocal) { sf &= ~ARC_SF_LOCALMASK; } + + WriteByte(MSG_ENTITY, sf); + + if(sf & ARC_SF_SETTINGS) // settings information + { + WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_degreespersegment)); + WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_distancepersegment)); + WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_maxangle)); + WriteCoord(MSG_ENTITY, WEP_CVAR(arc, beam_range)); + WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_returnspeed)); + WriteByte(MSG_ENTITY, WEP_CVAR(arc, beam_tightness) * 10); + + WriteByte(MSG_ENTITY, drawlocal); + } + if(sf & ARC_SF_START) // starting location + { + WriteCoord(MSG_ENTITY, self.beam_start_x); + WriteCoord(MSG_ENTITY, self.beam_start_y); + WriteCoord(MSG_ENTITY, self.beam_start_z); + } + if(sf & ARC_SF_WANTDIR) // want/aim direction + { + WriteCoord(MSG_ENTITY, self.beam_wantdir_x); + WriteCoord(MSG_ENTITY, self.beam_wantdir_y); + WriteCoord(MSG_ENTITY, self.beam_wantdir_z); + } + if(sf & ARC_SF_BEAMDIR) // beam direction + { + WriteCoord(MSG_ENTITY, self.beam_dir_x); + WriteCoord(MSG_ENTITY, self.beam_dir_y); + WriteCoord(MSG_ENTITY, self.beam_dir_z); + } + if(sf & ARC_SF_BEAMTYPE) // beam type + { + WriteByte(MSG_ENTITY, self.beam_type); + } + + return TRUE; +} + +void Reset_ArcBeam(entity player, vector forward) +{ + if (!player.arc_beam) { + return; + } + player.arc_beam.beam_dir = forward; + player.arc_beam.beam_teleporttime = time; +} + +void W_Arc_Beam_Think(void) +{ + if(self != self.owner.arc_beam) + { + remove(self); + return; + } + + if( + !IS_PLAYER(self.owner) + || + (self.owner.WEP_AMMO(ARC) <= 0 && !(self.owner.items & IT_UNLIMITED_WEAPON_AMMO)) + || + self.owner.deadflag != DEAD_NO + || + (!self.owner.BUTTON_ATCK /* FIXME(Samual): && !self.beam_bursting */) + || - self.owner.freezetag_frozen ++ self.owner.frozen + ) + { + if(self == self.owner.arc_beam) { self.owner.arc_beam = world; } + entity oldself = self; + self = self.owner; + if(!WEP_ACTION(WEP_ARC, WR_CHECKAMMO1) && !WEP_ACTION(WEP_ARC, WR_CHECKAMMO2)) + { + // note: this doesn't force the switch + W_SwitchToOtherWeapon(self); + } + self = oldself; + remove(self); + return; + } + + float burst = 0; + if(/*self.owner.BUTTON_ATCK2 || */self.beam_bursting) + { + if(!self.beam_bursting) + self.beam_bursting = TRUE; + burst = ARC_BT_BURSTMASK; + } + + // decrease ammo + float coefficient = frametime; + if(!(self.owner.items & IT_UNLIMITED_WEAPON_AMMO)) + { + float rootammo; + if(burst) + { rootammo = WEP_CVAR(arc, burst_ammo); } + else + { rootammo = WEP_CVAR(arc, beam_ammo); } + + if(rootammo) + { + coefficient = min(coefficient, self.owner.WEP_AMMO(ARC) / rootammo); + self.owner.WEP_AMMO(ARC) = max(0, self.owner.WEP_AMMO(ARC) - (rootammo * frametime)); + } + } + + makevectors(self.owner.v_angle); + + W_SetupShot_Range( + self.owner, + TRUE, + 0, + "", + 0, + WEP_CVAR(arc, beam_damage) * coefficient, + WEP_CVAR(arc, beam_range) + ); + + // After teleport, "lock" the beam until the teleport is confirmed. + if (time < self.beam_teleporttime + ANTILAG_LATENCY(self.owner)) { + w_shotdir = self.beam_dir; + } + + // network information: shot origin and want/aim direction + if(self.beam_start != w_shotorg) + { + self.SendFlags |= ARC_SF_START; + self.beam_start = w_shotorg; + } + if(self.beam_wantdir != w_shotdir) + { + self.SendFlags |= ARC_SF_WANTDIR; + self.beam_wantdir = w_shotdir; + } + + if(!self.beam_initialized) + { + self.beam_dir = w_shotdir; + self.beam_initialized = TRUE; + } + + // WEAPONTODO: Detect player velocity so that the beam curves when moving too + // idea: blend together self.beam_dir with the inverted direction the player is moving in + // might have to make some special accomodation so that it only uses view_right and view_up + + // note that if we do this, it'll always be corrected to a maximum angle by beam_maxangle handling + + float segments; + if(self.beam_dir != w_shotdir) + { + // calculate how much we're going to move the end of the beam to the want position + // WEAPONTODO (server and client): + // blendfactor never actually becomes 0 in this situation, which is a problem + // regarding precision... this means that self.beam_dir and w_shotdir approach + // eachother, however they never actually become the same value with this method. + // Perhaps we should do some form of rounding/snapping? + float angle = vlen(w_shotdir - self.beam_dir) * RAD2DEG; + if(angle && (angle > WEP_CVAR(arc, beam_maxangle))) + { + // if the angle is greater than maxangle, force the blendfactor to make this the maximum factor + float blendfactor = bound( + 0, + (1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)), + min(WEP_CVAR(arc, beam_maxangle) / angle, 1) + ); + self.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (self.beam_dir * blendfactor)); + } + else + { + // the radius is not too far yet, no worries :D + float blendfactor = bound( + 0, + (1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)), + 1 + ); + self.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (self.beam_dir * blendfactor)); + } + + // network information: beam direction + self.SendFlags |= ARC_SF_BEAMDIR; + + // calculate how many segments are needed + float max_allowed_segments; + + if(WEP_CVAR(arc, beam_distancepersegment)) + { + max_allowed_segments = min( + ARC_MAX_SEGMENTS, + 1 + (vlen(w_shotdir / WEP_CVAR(arc, beam_distancepersegment))) + ); + } + else { max_allowed_segments = ARC_MAX_SEGMENTS; } + + if(WEP_CVAR(arc, beam_degreespersegment)) + { + segments = bound( + 1, + ( + min( + angle, + WEP_CVAR(arc, beam_maxangle) + ) + / + WEP_CVAR(arc, beam_degreespersegment) + ), + max_allowed_segments + ); + } + else { segments = 1; } + } + else { segments = 1; } + + vector beam_endpos = (w_shotorg + (self.beam_dir * WEP_CVAR(arc, beam_range))); + vector beam_controlpoint = w_shotorg + w_shotdir * (WEP_CVAR(arc, beam_range) * (1 - WEP_CVAR(arc, beam_tightness))); + + float i; + float new_beam_type = 0; + vector last_origin = w_shotorg; + for(i = 1; i <= segments; ++i) + { + // WEAPONTODO (client): + // In order to do nice fading and pointing on the starting segment, we must always + // have that drawn as a separate triangle... However, that is difficult to do when + // keeping in mind the above problems and also optimizing the amount of segments + // drawn on screen at any given time. (Automatic beam quality scaling, essentially) + + vector new_origin = bezier_quadratic_getpoint( + w_shotorg, + beam_controlpoint, + beam_endpos, + i / segments); + vector new_dir = normalize(new_origin - last_origin); + + WarpZone_traceline_antilag( + self.owner, + last_origin, + new_origin, + MOVE_NORMAL, + self.owner, + ANTILAG_LATENCY(self.owner) + ); + + // Do all the transforms for warpzones right now, as we already + // "are" in the post-trace system (if we hit a player, that's + // always BEHIND the last passed wz). + last_origin = trace_endpos; + w_shotorg = WarpZone_TransformOrigin(WarpZone_trace_transform, w_shotorg); + beam_controlpoint = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_controlpoint); + beam_endpos = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_endpos); + new_dir = WarpZone_TransformVelocity(WarpZone_trace_transform, new_dir); + + float is_player = ( + trace_ent.classname == "player" + || + trace_ent.classname == "body" + || + (trace_ent.flags & FL_MONSTER) + ); + + if(trace_ent && trace_ent.takedamage && (is_player || WEP_CVAR(arc, beam_nonplayerdamage))) + { + // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?) + // NO. trace_endpos should be just fine. If not, + // that's an engine bug that needs proper debugging. + vector hitorigin = trace_endpos; + + float falloff = ExponentialFalloff( + WEP_CVAR(arc, beam_falloff_mindist), + WEP_CVAR(arc, beam_falloff_maxdist), + WEP_CVAR(arc, beam_falloff_halflifedist), + vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, hitorigin) - w_shotorg) + ); + + if(is_player && SAME_TEAM(self.owner, trace_ent)) + { + float roothealth, rootarmor; + if(burst) + { + roothealth = WEP_CVAR(arc, burst_healing_hps); + rootarmor = WEP_CVAR(arc, burst_healing_aps); + } + else + { + roothealth = WEP_CVAR(arc, beam_healing_hps); + rootarmor = WEP_CVAR(arc, beam_healing_aps); + } + + if(trace_ent.health <= WEP_CVAR(arc, beam_healing_hmax) && roothealth) + { + trace_ent.health = min( + trace_ent.health + (roothealth * coefficient), + WEP_CVAR(arc, beam_healing_hmax) + ); + } + if(trace_ent.armorvalue <= WEP_CVAR(arc, beam_healing_amax) && rootarmor) + { + trace_ent.armorvalue = min( + trace_ent.armorvalue + (rootarmor * coefficient), + WEP_CVAR(arc, beam_healing_amax) + ); + } + + // stop rot, set visual effect + if(roothealth || rootarmor) + { + trace_ent.pauserothealth_finished = max( + trace_ent.pauserothealth_finished, + time + autocvar_g_balance_pause_health_rot + ); + trace_ent.pauserotarmor_finished = max( + trace_ent.pauserotarmor_finished, + time + autocvar_g_balance_pause_armor_rot + ); + new_beam_type = ARC_BT_HEAL; + } + } + else + { + float rootdamage; + if(is_player) + { + if(burst) + { rootdamage = WEP_CVAR(arc, burst_damage); } + else + { rootdamage = WEP_CVAR(arc, beam_damage); } + } + else + { rootdamage = WEP_CVAR(arc, beam_nonplayerdamage); } + + if(accuracy_isgooddamage(self.owner, trace_ent)) + { + accuracy_add( + self.owner, + WEP_ARC, + 0, + rootdamage * coefficient * falloff + ); + } + + Damage( + trace_ent, + self.owner, + self.owner, + rootdamage * coefficient * falloff, + WEP_ARC, + hitorigin, + WEP_CVAR(arc, beam_force) * new_dir * coefficient * falloff + ); + + new_beam_type = ARC_BT_HIT; + } + break; + } + else if(trace_fraction != 1) + { + // we collided with geometry + new_beam_type = ARC_BT_WALL; + break; + } + } + + // te_explosion(trace_endpos); + + // if we're bursting, use burst visual effects + new_beam_type += burst; + + // network information: beam type + if(new_beam_type != self.beam_type) + { + self.SendFlags |= ARC_SF_BEAMTYPE; + self.beam_type = new_beam_type; + } + + self.owner.beam_prev = time; + self.nextthink = time; +} + +void W_Arc_Beam(float burst) +{ + // FIXME(Samual): remove this when overheat and burst work. + if (burst) + { + centerprint(self, "^4NOTE:^7 Arc burst (secondary) is not implemented yet."); + } + + // only play fire sound if 1 sec has passed since player let go the fire button + if(time - self.beam_prev > 1) + { + sound(self, CH_WEAPON_A, "weapons/lgbeam_fire.wav", VOL_BASE, ATTN_NORM); + } + + entity beam = self.arc_beam = spawn(); + beam.classname = "W_Arc_Beam"; + beam.solid = SOLID_NOT; + beam.think = W_Arc_Beam_Think; + beam.owner = self; + beam.movetype = MOVETYPE_NONE; + beam.bot_dodge = TRUE; + beam.bot_dodgerating = WEP_CVAR(arc, beam_damage); + beam.beam_bursting = burst; + Net_LinkEntity(beam, FALSE, 0, W_Arc_Beam_Send); + + entity oldself = self; + self = beam; + self.think(); + self = oldself; +} + +float W_Arc(float req) +{ + switch(req) + { + case WR_AIM: + { + if(WEP_CVAR(arc, beam_botaimspeed)) + { + self.BUTTON_ATCK = bot_aim( + WEP_CVAR(arc, beam_botaimspeed), + 0, + WEP_CVAR(arc, beam_botaimlifetime), + FALSE + ); + } + else + { + self.BUTTON_ATCK = bot_aim( + 1000000, + 0, + 0.001, + FALSE + ); + } + return TRUE; + } + case WR_THINK: + { + #if 0 + if(self.arc_beam.beam_heat > threshold) + { + stop the beam somehow + play overheat animation + } + #endif + + if(self.BUTTON_ATCK || self.BUTTON_ATCK2 /* FIXME(Samual): || self.arc_beam.beam_bursting */) + { + if(self.BUTTON_ATCK_prev) + { + #if 0 + if(self.animstate_startframe == self.anim_shoot_x && self.animstate_numframes == self.anim_shoot_y) + weapon_thinkf(WFRAME_DONTCHANGE, autocvar_g_balance_arc_primary_animtime, w_ready); + else + #endif + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready); + } + + if((!self.arc_beam) || wasfreed(self.arc_beam)) + { + if(weapon_prepareattack(!!self.BUTTON_ATCK2, 0)) + { + W_Arc_Beam(!!self.BUTTON_ATCK2); + + if(!self.BUTTON_ATCK_prev) + { + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready); + self.BUTTON_ATCK_prev = 1; + } + } + } + } + else // todo + { + if(self.BUTTON_ATCK_prev != 0) + { + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready); + ATTACK_FINISHED(self) = time + WEP_CVAR(arc, beam_refire) * W_WeaponRateFactor(); + } + self.BUTTON_ATCK_prev = 0; + } + + #if 0 + if(self.BUTTON_ATCK2) + if(weapon_prepareattack(1, autocvar_g_balance_arc_secondary_refire)) + { + W_Arc_Attack2(); + self.arc_count = autocvar_g_balance_arc_secondary_count; + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_arc_secondary_animtime, w_arc_checkattack); + self.arc_secondarytime = time + autocvar_g_balance_arc_secondary_refire2 * W_WeaponRateFactor(); + } + #endif + + return TRUE; + } + case WR_INIT: + { + precache_model("models/weapons/g_arc.md3"); + precache_model("models/weapons/v_arc.md3"); + precache_model("models/weapons/h_arc.iqm"); + precache_sound("weapons/lgbeam_fire.wav"); + if(!arc_shotorigin[0]) + { + arc_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 1); + arc_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 2); + arc_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 3); + arc_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 4); + } + ARC_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) + return TRUE; + } + case WR_CHECKAMMO1: + { + return ((!WEP_CVAR(arc, beam_ammo)) || (self.WEP_AMMO(ARC) > 0)); + } + case WR_CHECKAMMO2: + { + // arc currently has no secondary attack + return FALSE; + //return ((!WEP_CVAR(arc, burst_ammo)) || (self.WEP_AMMO(ARC) > 0)); + } + case WR_CONFIG: + { + ARC_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS) + return TRUE; + } + case WR_KILLMESSAGE: + { + return WEAPON_ARC_MURDER; + } + } + return FALSE; +} +#endif +#ifdef CSQC +void Draw_ArcBeam_callback(vector start, vector hit, vector end) +{ + entity beam = Draw_ArcBeam_callback_entity; + vector transformed_view_org; + transformed_view_org = WarpZone_TransformOrigin(WarpZone_trace_transform, view_origin); + + // Thickdir shall be perpendicular to the beam and to the view-to-beam direction (WEAPONTODO: WHY) + // WEAPONTODO: Wouldn't it be better to be perpendicular to the beam and to the view FORWARD direction? + vector thickdir = normalize(cross(normalize(start - hit), transformed_view_org - start)); + + vector hitorigin; + + // draw segment + #if 0 + if(trace_fraction != 1) + { + // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?) + hitorigin = start + (Draw_ArcBeam_callback_new_dir * Draw_ArcBeam_callback_segmentdist * trace_fraction); + hitorigin = WarpZone_TransformOrigin(WarpZone_trace_transform, hitorigin); + } + else + { + hitorigin = hit; + } + #else + hitorigin = hit; + #endif + + // decide upon thickness + float thickness = beam.beam_thickness; + + // draw primary beam render + vector top = hitorigin + (thickdir * thickness); + vector bottom = hitorigin - (thickdir * thickness); + + vector last_top = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_top); + vector last_bottom = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_bottom); + + R_BeginPolygon(beam.beam_image, DRAWFLAG_NORMAL); // DRAWFLAG_ADDITIVE + R_PolygonVertex( + top, + '0 0.5 0' + ('0 0.5 0' * (thickness / beam.beam_thickness)), + beam.beam_color, + beam.beam_alpha + ); + R_PolygonVertex( + last_top, + '0 0.5 0' + ('0 0.5 0' * (Draw_ArcBeam_callback_last_thickness / beam.beam_thickness)), + beam.beam_color, + beam.beam_alpha + ); + R_PolygonVertex( + last_bottom, + '0 0.5 0' * (1 - (Draw_ArcBeam_callback_last_thickness / beam.beam_thickness)), + beam.beam_color, + beam.beam_alpha + ); + R_PolygonVertex( + bottom, + '0 0.5 0' * (1 - (thickness / beam.beam_thickness)), + beam.beam_color, + beam.beam_alpha + ); + R_EndPolygon(); + + // draw trailing particles + // NOTES: + // - Don't use spammy particle counts here, use a FEW small particles around the beam + // - We're not using WarpZone_TrailParticles here because we will handle warpzones ourselves. + if(beam.beam_traileffect) + { + trailparticles(beam, beam.beam_traileffect, start, hitorigin); + } + + // set up for the next + Draw_ArcBeam_callback_last_thickness = thickness; + Draw_ArcBeam_callback_last_top = WarpZone_UnTransformOrigin(WarpZone_trace_transform, top); + Draw_ArcBeam_callback_last_bottom = WarpZone_UnTransformOrigin(WarpZone_trace_transform, bottom); +} + +void Reset_ArcBeam(void) +{ + entity e; + for (e = world; (e = findfloat(e, beam_usevieworigin, 1)); ) { + e.beam_initialized = FALSE; + } + for (e = world; (e = findfloat(e, beam_usevieworigin, 2)); ) { + e.beam_initialized = FALSE; + } +} + +void Draw_ArcBeam(void) +{ + if(!self.beam_usevieworigin) + { + InterpolateOrigin_Do(); + } + + // origin = beam starting origin + // v_angle = wanted/aim direction + // angles = current direction of beam + + vector start_pos; + vector wantdir; //= view_forward; + vector beamdir; //= self.beam_dir; + + float segments; + if(self.beam_usevieworigin) + { + // WEAPONTODO: + // Currently we have to replicate nearly the same method of figuring + // out the shotdir that the server does... Ideally in the future we + // should be able to acquire this from a generalized function built + // into a weapon system for client code. + + // find where we are aiming + makevectors(warpzone_save_view_angles); + vector forward = v_forward; + vector right = v_right; + vector up = v_up; + + // decide upon start position + if(self.beam_usevieworigin == 2) + { start_pos = warpzone_save_view_origin; } + else + { start_pos = self.origin; } + + // trace forward with an estimation + WarpZone_TraceLine( + start_pos, + start_pos + forward * self.beam_range, + MOVE_NOMONSTERS, + self + ); + + // untransform in case our trace went through a warpzone + vector end_pos = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos); + + // un-adjust trueaim if shotend is too close + if(vlen(end_pos - start_pos) < g_trueaim_minrange) + end_pos = start_pos + (forward * g_trueaim_minrange); + + // move shot origin to the actual gun muzzle origin + vector origin_offset = + right * -self.beam_shotorigin_y + + up * self.beam_shotorigin_z; + + start_pos = start_pos + origin_offset; + + // Move it also forward, but only as far as possible without hitting anything. Don't poke into walls! + traceline(start_pos, start_pos + forward * self.beam_shotorigin_x, MOVE_NORMAL, self); + start_pos = trace_endpos; + + // calculate the aim direction now + wantdir = normalize(end_pos - start_pos); + + if(!self.beam_initialized) + { + self.beam_dir = wantdir; + self.beam_initialized = TRUE; + } + + if(self.beam_dir != wantdir) + { + // calculate how much we're going to move the end of the beam to the want position + // WEAPONTODO (server and client): + // blendfactor never actually becomes 0 in this situation, which is a problem + // regarding precision... this means that self.beam_dir and w_shotdir approach + // eachother, however they never actually become the same value with this method. + // Perhaps we should do some form of rounding/snapping? + float angle = vlen(wantdir - self.beam_dir) * RAD2DEG; + if(angle && (angle > self.beam_maxangle)) + { + // if the angle is greater than maxangle, force the blendfactor to make this the maximum factor + float blendfactor = bound( + 0, + (1 - (self.beam_returnspeed * frametime)), + min(self.beam_maxangle / angle, 1) + ); + self.beam_dir = normalize((wantdir * (1 - blendfactor)) + (self.beam_dir * blendfactor)); + } + else + { + // the radius is not too far yet, no worries :D + float blendfactor = bound( + 0, + (1 - (self.beam_returnspeed * frametime)), + 1 + ); + self.beam_dir = normalize((wantdir * (1 - blendfactor)) + (self.beam_dir * blendfactor)); + } + + // calculate how many segments are needed + float max_allowed_segments; + + if(self.beam_distancepersegment) + { + max_allowed_segments = min( + ARC_MAX_SEGMENTS, + 1 + (vlen(wantdir / self.beam_distancepersegment)) + ); + } + else { max_allowed_segments = ARC_MAX_SEGMENTS; } + + if(self.beam_degreespersegment) + { + segments = bound( + 1, + ( + min( + angle, + self.beam_maxangle + ) + / + self.beam_degreespersegment + ), + max_allowed_segments + ); + } + else { segments = 1; } + } + else { segments = 1; } + + // set the beam direction which the rest of the code will refer to + beamdir = self.beam_dir; + + // finally, set self.angles to the proper direction so that muzzle attachment points in proper direction + self.angles = fixedvectoangles2(forward, up); // TODO(Samual): is this == warpzone_save_view_angles? + } + else + { + // set the values from the provided info from the networked entity + start_pos = self.origin; + wantdir = self.v_angle; + beamdir = self.angles; + + if(beamdir != wantdir) + { + float angle = vlen(wantdir - beamdir) * RAD2DEG; + + // calculate how many segments are needed + float max_allowed_segments; + + if(self.beam_distancepersegment) + { + max_allowed_segments = min( + ARC_MAX_SEGMENTS, + 1 + (vlen(wantdir / self.beam_distancepersegment)) + ); + } + else { max_allowed_segments = ARC_MAX_SEGMENTS; } + + if(self.beam_degreespersegment) + { + segments = bound( + 1, + ( + min( + angle, + self.beam_maxangle + ) + / + self.beam_degreespersegment + ), + max_allowed_segments + ); + } + else { segments = 1; } + } + else { segments = 1; } + } + + setorigin(self, start_pos); + self.beam_muzzleentity.angles_z = random() * 360; // WEAPONTODO: use avelocity instead? + + vector beam_endpos = (start_pos + (beamdir * self.beam_range)); + vector beam_controlpoint = start_pos + wantdir * (self.beam_range * (1 - self.beam_tightness)); + + Draw_ArcBeam_callback_entity = self; + Draw_ArcBeam_callback_last_thickness = 0; + Draw_ArcBeam_callback_last_top = start_pos; + Draw_ArcBeam_callback_last_bottom = start_pos; + + vector last_origin = start_pos; + vector original_start_pos = start_pos; + + float i; + for(i = 1; i <= segments; ++i) + { + // WEAPONTODO (client): + // In order to do nice fading and pointing on the starting segment, we must always + // have that drawn as a separate triangle... However, that is difficult to do when + // keeping in mind the above problems and also optimizing the amount of segments + // drawn on screen at any given time. (Automatic beam quality scaling, essentially) + + vector new_origin = bezier_quadratic_getpoint( + start_pos, + beam_controlpoint, + beam_endpos, + i / segments); + + WarpZone_TraceBox_ThroughZone( + last_origin, + '0 0 0', + '0 0 0', + new_origin, + MOVE_NORMAL, + world, + world, + Draw_ArcBeam_callback + ); + + // Do all the transforms for warpzones right now, as we already "are" in the post-trace + // system (if we hit a player, that's always BEHIND the last passed wz). + last_origin = trace_endpos; + start_pos = WarpZone_TransformOrigin(WarpZone_trace_transform, start_pos); + beam_controlpoint = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_controlpoint); + beam_endpos = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_endpos); + beamdir = WarpZone_TransformVelocity(WarpZone_trace_transform, beamdir); + Draw_ArcBeam_callback_last_top = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_top); + Draw_ArcBeam_callback_last_bottom = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_bottom); + + if(trace_fraction < 1) { break; } + } + + // visual effects for startpoint and endpoint + if(self.beam_hiteffect) + { + // FIXME we really should do this on the server so it actually + // matches gameplay. What this client side stuff is doing is no + // more than guesswork. + pointparticles( + self.beam_hiteffect, + last_origin, + beamdir * -1, + frametime * 2 + ); + } + if(self.beam_hitlight[0]) + { + adddynamiclight( + last_origin, + self.beam_hitlight[0], + vec3( + self.beam_hitlight[1], + self.beam_hitlight[2], + self.beam_hitlight[3] + ) + ); + } + if(self.beam_muzzleeffect) + { + pointparticles( + self.beam_muzzleeffect, + original_start_pos + wantdir * 20, + wantdir * 1000, + frametime * 0.1 + ); + } + if(self.beam_muzzlelight[0]) + { + adddynamiclight( + original_start_pos + wantdir * 20, + self.beam_muzzlelight[0], + vec3( + self.beam_muzzlelight[1], + self.beam_muzzlelight[2], + self.beam_muzzlelight[3] + ) + ); + } + + // cleanup + Draw_ArcBeam_callback_entity = world; + Draw_ArcBeam_callback_last_thickness = 0; + Draw_ArcBeam_callback_last_top = '0 0 0'; + Draw_ArcBeam_callback_last_bottom = '0 0 0'; +} + +void Remove_ArcBeam(void) +{ + remove(self.beam_muzzleentity); + sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM); +} + +void Ent_ReadArcBeam(float isnew) +{ + float sf = ReadByte(); + entity flash; + + if(isnew) + { + // calculate shot origin offset from gun alignment + float gunalign = autocvar_cl_gunalign; + if(gunalign != 1 && gunalign != 2 && gunalign != 4) + gunalign = 3; // default value + --gunalign; + + self.beam_shotorigin = arc_shotorigin[gunalign]; + + // set other main attributes of the beam + self.draw = Draw_ArcBeam; + self.entremove = Remove_ArcBeam; + sound(self, CH_SHOTS_SINGLE, "weapons/lgbeam_fly.wav", VOL_BASE, ATTEN_NORM); + + flash = spawn(); + flash.owner = self; + flash.effects = EF_ADDITIVE | EF_FULLBRIGHT; + flash.drawmask = MASK_NORMAL; + flash.solid = SOLID_NOT; + flash.avelocity_z = 5000; + setattachment(flash, self, ""); + setorigin(flash, '0 0 0'); + + self.beam_muzzleentity = flash; + } + else + { + flash = self.beam_muzzleentity; + } + + if(sf & ARC_SF_SETTINGS) // settings information + { + self.beam_degreespersegment = ReadShort(); + self.beam_distancepersegment = ReadShort(); + self.beam_maxangle = ReadShort(); + self.beam_range = ReadCoord(); + self.beam_returnspeed = ReadShort(); + self.beam_tightness = (ReadByte() / 10); + + if(ReadByte()) + { + if(autocvar_chase_active) + { self.beam_usevieworigin = 1; } + else // use view origin + { self.beam_usevieworigin = 2; } + } + else + { + self.beam_usevieworigin = 0; + } + } + + if(!self.beam_usevieworigin) + { + // self.iflags = IFLAG_ORIGIN | IFLAG_ANGLES | IFLAG_V_ANGLE; // why doesn't this work? + self.iflags = IFLAG_ORIGIN; + + InterpolateOrigin_Undo(); + } + + if(sf & ARC_SF_START) // starting location + { + self.origin_x = ReadCoord(); + self.origin_y = ReadCoord(); + self.origin_z = ReadCoord(); + } + else if(self.beam_usevieworigin) // infer the location from player location + { + if(self.beam_usevieworigin == 2) + { + // use view origin + self.origin = view_origin; + } + else + { + // use player origin so that third person display still works + self.origin = getplayerorigin(player_localnum) + ('0 0 1' * getstati(STAT_VIEWHEIGHT)); + } + } + + setorigin(self, self.origin); + + if(sf & ARC_SF_WANTDIR) // want/aim direction + { + self.v_angle_x = ReadCoord(); + self.v_angle_y = ReadCoord(); + self.v_angle_z = ReadCoord(); + } + + if(sf & ARC_SF_BEAMDIR) // beam direction + { + self.angles_x = ReadCoord(); + self.angles_y = ReadCoord(); + self.angles_z = ReadCoord(); + } + + if(sf & ARC_SF_BEAMTYPE) // beam type + { + self.beam_type = ReadByte(); + switch(self.beam_type) + { + case ARC_BT_MISS: + { + self.beam_color = '-1 -1 1'; + self.beam_alpha = 0.5; + self.beam_thickness = 8; + self.beam_traileffect = FALSE; + self.beam_hiteffect = particleeffectnum("electro_lightning"); + self.beam_hitlight[0] = 0; + self.beam_hitlight[1] = 1; + self.beam_hitlight[2] = 1; + self.beam_hitlight[3] = 1; + self.beam_muzzleeffect = -1; //particleeffectnum("nex_muzzleflash"); + self.beam_muzzlelight[0] = 0; + self.beam_muzzlelight[1] = 1; + self.beam_muzzlelight[2] = 1; + self.beam_muzzlelight[3] = 1; + if(self.beam_muzzleeffect >= 0) + { + self.beam_image = "particles/lgbeam"; + setmodel(flash, "models/flash.md3"); + flash.alpha = self.beam_alpha; + flash.colormod = self.beam_color; + flash.scale = 0.5; + } + break; + } + case ARC_BT_WALL: // grenadelauncher_muzzleflash healray_muzzleflash + { + self.beam_color = '0.5 0.5 1'; + self.beam_alpha = 0.5; + self.beam_thickness = 8; + self.beam_traileffect = FALSE; + self.beam_hiteffect = particleeffectnum("electro_lightning"); + self.beam_hitlight[0] = 0; + self.beam_hitlight[1] = 1; + self.beam_hitlight[2] = 1; + self.beam_hitlight[3] = 1; + self.beam_muzzleeffect = -1; // particleeffectnum("grenadelauncher_muzzleflash"); + self.beam_muzzlelight[0] = 0; + self.beam_muzzlelight[1] = 1; + self.beam_muzzlelight[2] = 1; + self.beam_muzzlelight[3] = 1; + self.beam_image = "particles/lgbeam"; + if(self.beam_muzzleeffect >= 0) + { + setmodel(flash, "models/flash.md3"); + flash.alpha = self.beam_alpha; + flash.colormod = self.beam_color; + flash.scale = 0.5; + } + break; + } + case ARC_BT_HEAL: + { + self.beam_color = '0 1 0'; + self.beam_alpha = 0.5; + self.beam_thickness = 8; + self.beam_traileffect = FALSE; + self.beam_hiteffect = particleeffectnum("healray_impact"); + self.beam_hitlight[0] = 0; + self.beam_hitlight[1] = 1; + self.beam_hitlight[2] = 1; + self.beam_hitlight[3] = 1; + self.beam_muzzleeffect = -1; //particleeffectnum("nex_muzzleflash"); + self.beam_muzzlelight[0] = 0; + self.beam_muzzlelight[1] = 1; + self.beam_muzzlelight[2] = 1; + self.beam_muzzlelight[3] = 1; + self.beam_image = "particles/lgbeam"; + if(self.beam_muzzleeffect >= 0) + { + self.beam_image = "particles/lgbeam"; + setmodel(flash, "models/flash.md3"); + flash.alpha = self.beam_alpha; + flash.colormod = self.beam_color; + flash.scale = 0.5; + } + break; + } + case ARC_BT_HIT: + { + self.beam_color = '1 0 1'; + self.beam_alpha = 0.5; + self.beam_thickness = 8; + self.beam_traileffect = FALSE; + self.beam_hiteffect = particleeffectnum("electro_lightning"); + self.beam_hitlight[0] = 20; + self.beam_hitlight[1] = 1; + self.beam_hitlight[2] = 0; + self.beam_hitlight[3] = 0; + self.beam_muzzleeffect = -1; //particleeffectnum("nex_muzzleflash"); + self.beam_muzzlelight[0] = 50; + self.beam_muzzlelight[1] = 1; + self.beam_muzzlelight[2] = 0; + self.beam_muzzlelight[3] = 0; + self.beam_image = "particles/lgbeam"; + if(self.beam_muzzleeffect >= 0) + { + self.beam_image = "particles/lgbeam"; + setmodel(flash, "models/flash.md3"); + flash.alpha = self.beam_alpha; + flash.colormod = self.beam_color; + flash.scale = 0.5; + } + break; + } + case ARC_BT_BURST_MISS: + { + self.beam_color = '-1 -1 1'; + self.beam_alpha = 0.5; + self.beam_thickness = 14; + self.beam_traileffect = FALSE; + self.beam_hiteffect = particleeffectnum("electro_lightning"); + self.beam_hitlight[0] = 0; + self.beam_hitlight[1] = 1; + self.beam_hitlight[2] = 1; + self.beam_hitlight[3] = 1; + self.beam_muzzleeffect = -1; //particleeffectnum("nex_muzzleflash"); + self.beam_muzzlelight[0] = 0; + self.beam_muzzlelight[1] = 1; + self.beam_muzzlelight[2] = 1; + self.beam_muzzlelight[3] = 1; + self.beam_image = "particles/lgbeam"; + setmodel(flash, "models/flash.md3"); + flash.alpha = self.beam_alpha; + flash.colormod = self.beam_color; + flash.scale = 0.5; + break; + } + case ARC_BT_BURST_WALL: + { + self.beam_color = '0.5 0.5 1'; + self.beam_alpha = 0.5; + self.beam_thickness = 14; + self.beam_traileffect = FALSE; + self.beam_hiteffect = particleeffectnum("electro_lightning"); + self.beam_hitlight[0] = 0; + self.beam_hitlight[1] = 1; + self.beam_hitlight[2] = 1; + self.beam_hitlight[3] = 1; + self.beam_muzzleeffect = -1; //particleeffectnum("nex_muzzleflash"); + self.beam_muzzlelight[0] = 0; + self.beam_muzzlelight[1] = 1; + self.beam_muzzlelight[2] = 1; + self.beam_muzzlelight[3] = 1; + self.beam_image = "particles/lgbeam"; + if(self.beam_muzzleeffect >= 0) + { + self.beam_image = "particles/lgbeam"; + setmodel(flash, "models/flash.md3"); + flash.alpha = self.beam_alpha; + flash.colormod = self.beam_color; + flash.scale = 0.5; + } + break; + } + case ARC_BT_BURST_HEAL: + { + self.beam_color = '0 1 0'; + self.beam_alpha = 0.5; + self.beam_thickness = 14; + self.beam_traileffect = FALSE; + self.beam_hiteffect = particleeffectnum("electro_lightning"); + self.beam_hitlight[0] = 0; + self.beam_hitlight[1] = 1; + self.beam_hitlight[2] = 1; + self.beam_hitlight[3] = 1; + self.beam_muzzleeffect = -1; //particleeffectnum("nex_muzzleflash"); + self.beam_muzzlelight[0] = 0; + self.beam_muzzlelight[1] = 1; + self.beam_muzzlelight[2] = 1; + self.beam_muzzlelight[3] = 1; + self.beam_image = "particles/lgbeam"; + if(self.beam_muzzleeffect >= 0) + { + self.beam_image = "particles/lgbeam"; + setmodel(flash, "models/flash.md3"); + flash.alpha = self.beam_alpha; + flash.colormod = self.beam_color; + flash.scale = 0.5; + } + break; + } + case ARC_BT_BURST_HIT: + { + self.beam_color = '1 0 1'; + self.beam_alpha = 0.5; + self.beam_thickness = 14; + self.beam_traileffect = FALSE; + self.beam_hiteffect = particleeffectnum("electro_lightning"); + self.beam_hitlight[0] = 0; + self.beam_hitlight[1] = 1; + self.beam_hitlight[2] = 1; + self.beam_hitlight[3] = 1; + self.beam_muzzleeffect = -1; //particleeffectnum("nex_muzzleflash"); + self.beam_muzzlelight[0] = 0; + self.beam_muzzlelight[1] = 1; + self.beam_muzzlelight[2] = 1; + self.beam_muzzlelight[3] = 1; + self.beam_image = "particles/lgbeam"; + if(self.beam_muzzleeffect >= 0) + { + self.beam_image = "particles/lgbeam"; + setmodel(flash, "models/flash.md3"); + flash.alpha = self.beam_alpha; + flash.colormod = self.beam_color; + flash.scale = 0.5; + } + break; + } + + // shouldn't be possible, but lets make it colorful if it does :D + default: + { + self.beam_color = randomvec(); + self.beam_alpha = 1; + self.beam_thickness = 8; + self.beam_traileffect = FALSE; + self.beam_hiteffect = FALSE; + self.beam_hitlight[0] = 0; + self.beam_hitlight[1] = 1; + self.beam_hitlight[2] = 1; + self.beam_hitlight[3] = 1; + self.beam_muzzleeffect = -1; //particleeffectnum("nex_muzzleflash"); + self.beam_muzzlelight[0] = 0; + self.beam_muzzlelight[1] = 1; + self.beam_muzzlelight[2] = 1; + self.beam_muzzlelight[3] = 1; + self.beam_image = "particles/lgbeam"; + if(self.beam_muzzleeffect >= 0) + { + self.beam_image = "particles/lgbeam"; + setmodel(flash, "models/flash.md3"); + flash.alpha = self.beam_alpha; + flash.colormod = self.beam_color; + flash.scale = 0.5; + } + break; + } + } + } + + if(!self.beam_usevieworigin) + { + InterpolateOrigin_Note(); + } +} + +float W_Arc(float req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + // todo + return TRUE; + } + case WR_INIT: + { + precache_sound("weapons/lgbeam_fly.wav"); + return TRUE; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return FALSE; + } + } + return FALSE; +} +#endif +#endif diff --cc qcsrc/common/weapons/w_minelayer.qc index 198097bfb,000000000..e0e9c6256 mode 100644,000000..100644 --- a/qcsrc/common/weapons/w_minelayer.qc +++ b/qcsrc/common/weapons/w_minelayer.qc @@@ -1,620 -1,0 +1,620 @@@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ MINE_LAYER, +/* function */ W_MineLayer, +/* ammotype */ ammo_rockets, +/* impulse */ 4, +/* flags */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_HIGH, +/* color */ '0.75 1 0', +/* modelname */ "minelayer", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairminelayer 0.9", +/* wepimg */ "weaponminelayer", +/* refname */ "minelayer", +/* wepname */ _("Mine Layer") +); + +#define MINELAYER_SETTINGS(w_cvar,w_prop) MINELAYER_SETTINGS_LIST(w_cvar, w_prop, MINE_LAYER, minelayer) +#define MINELAYER_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, NONE, ammo) \ + w_cvar(id, sn, NONE, animtime) \ + w_cvar(id, sn, NONE, damage) \ + w_cvar(id, sn, NONE, damageforcescale) \ + w_cvar(id, sn, NONE, detonatedelay) \ + w_cvar(id, sn, NONE, edgedamage) \ + w_cvar(id, sn, NONE, force) \ + w_cvar(id, sn, NONE, health) \ + w_cvar(id, sn, NONE, lifetime) \ + w_cvar(id, sn, NONE, lifetime_countdown) \ + w_cvar(id, sn, NONE, limit) \ + w_cvar(id, sn, NONE, protection) \ + w_cvar(id, sn, NONE, proximityradius) \ + w_cvar(id, sn, NONE, radius) \ + w_cvar(id, sn, NONE, refire) \ + w_cvar(id, sn, NONE, remote_damage) \ + w_cvar(id, sn, NONE, remote_edgedamage) \ + w_cvar(id, sn, NONE, remote_force) \ + w_cvar(id, sn, NONE, remote_radius) \ + w_cvar(id, sn, NONE, speed) \ + w_cvar(id, sn, NONE, time) \ + w_prop(id, sn, float, reloading_ammo, reload_ammo) \ + w_prop(id, sn, float, reloading_time, reload_time) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +MINELAYER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +void W_MineLayer_Think(void); +.float minelayer_detonate, mine_explodeanyway; +.float mine_time; +.vector mine_orientation; +#endif +#else +#ifdef SVQC +void spawnfunc_weapon_minelayer(void) { weapon_defaultspawnfunc(WEP_MINE_LAYER); } + +void W_MineLayer_Stick(entity to) +{ + spamsound(self, CH_SHOTS, "weapons/mine_stick.wav", VOL_BASE, ATTN_NORM); + + // in order for mines to face properly when sticking to the ground, they must be a server side entity rather than a csqc projectile + + entity newmine; + newmine = spawn(); + newmine.classname = self.classname; + + newmine.bot_dodge = self.bot_dodge; + newmine.bot_dodgerating = self.bot_dodgerating; + + newmine.owner = self.owner; + newmine.realowner = self.realowner; + setsize(newmine, '-4 -4 -4', '4 4 4'); + setorigin(newmine, self.origin); + setmodel(newmine, "models/mine.md3"); + newmine.angles = vectoangles(-trace_plane_normal); // face against the surface + + newmine.mine_orientation = -trace_plane_normal; + + newmine.takedamage = self.takedamage; + newmine.damageforcescale = self.damageforcescale; + newmine.health = self.health; + newmine.event_damage = self.event_damage; + newmine.spawnshieldtime = self.spawnshieldtime; + newmine.damagedbycontents = TRUE; + + newmine.movetype = MOVETYPE_NONE; // lock the mine in place + newmine.projectiledeathtype = self.projectiledeathtype; + + newmine.mine_time = self.mine_time; + + newmine.touch = func_null; + newmine.think = W_MineLayer_Think; + newmine.nextthink = time; + newmine.cnt = self.cnt; + newmine.flags = self.flags; + + remove(self); + self = newmine; + + if(to) + SetMovetypeFollow(self, to); +} + +void W_MineLayer_Explode(void) +{ + if(other.takedamage == DAMAGE_AIM) + if(IS_PLAYER(other)) + if(DIFF_TEAM(self.realowner, other)) + if(other.deadflag == DEAD_NO) + if(IsFlying(other)) + Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT); + + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + RadiusDamage(self, self.realowner, WEP_CVAR(minelayer, damage), WEP_CVAR(minelayer, edgedamage), WEP_CVAR(minelayer, radius), world, world, WEP_CVAR(minelayer, force), self.projectiledeathtype, other); + + if(self.realowner.weapon == WEP_MINE_LAYER) + { + entity oldself; + oldself = self; + self = self.realowner; + if(!WEP_ACTION(WEP_MINE_LAYER, WR_CHECKAMMO1)) + { + self.cnt = WEP_MINE_LAYER; + ATTACK_FINISHED(self) = time; + self.switchweapon = w_getbestweapon(self); + } + self = oldself; + } + self.realowner.minelayer_mines -= 1; + remove(self); +} + +void W_MineLayer_DoRemoteExplode(void) +{ + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + if(self.movetype == MOVETYPE_NONE || self.movetype == MOVETYPE_FOLLOW) + self.velocity = self.mine_orientation; // particle fx and decals need .velocity + + RadiusDamage(self, self.realowner, WEP_CVAR(minelayer, remote_damage), WEP_CVAR(minelayer, remote_edgedamage), WEP_CVAR(minelayer, remote_radius), world, world, WEP_CVAR(minelayer, remote_force), self.projectiledeathtype | HITTYPE_BOUNCE, world); + + if(self.realowner.weapon == WEP_MINE_LAYER) + { + entity oldself; + oldself = self; + self = self.realowner; + if(!WEP_ACTION(WEP_MINE_LAYER, WR_CHECKAMMO1)) + { + self.cnt = WEP_MINE_LAYER; + ATTACK_FINISHED(self) = time; + self.switchweapon = w_getbestweapon(self); + } + self = oldself; + } + self.realowner.minelayer_mines -= 1; + remove(self); +} + +void W_MineLayer_RemoteExplode(void) +{ + if(self.realowner.deadflag == DEAD_NO) + if((self.spawnshieldtime >= 0) + ? (time >= self.spawnshieldtime) // timer + : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > WEP_CVAR(minelayer, remote_radius)) // safety device + ) + { + W_MineLayer_DoRemoteExplode(); + } +} + +void W_MineLayer_ProximityExplode(void) +{ + // make sure no friend is in the mine's radius. If there is any, explosion is delayed until he's at a safe distance + if(WEP_CVAR(minelayer, protection) && self.mine_explodeanyway == 0) + { + entity head; + head = findradius(self.origin, WEP_CVAR(minelayer, radius)); + while(head) + { + if(head == self.realowner || SAME_TEAM(head, self.realowner)) + return; + head = head.chain; + } + } + + self.mine_time = 0; + W_MineLayer_Explode(); +} + +float W_MineLayer_Count(entity e) +{ + float minecount = 0; + entity mine; + for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.realowner == e) + minecount += 1; + + return minecount; +} + +void W_MineLayer_Think(void) +{ + entity head; + + self.nextthink = time; + + if(self.movetype == MOVETYPE_FOLLOW) + { + if(LostMovetypeFollow(self)) + { + UnsetMovetypeFollow(self); + self.movetype = MOVETYPE_NONE; + } + } + + // our lifetime has expired, it's time to die - mine_time just allows us to play a sound for this + // TODO: replace this mine_trigger.wav sound with a real countdown + if((time > self.cnt) && (!self.mine_time)) + { + if(WEP_CVAR(minelayer, lifetime_countdown) > 0) + spamsound(self, CH_SHOTS, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM); + self.mine_time = time + WEP_CVAR(minelayer, lifetime_countdown); + self.mine_explodeanyway = 1; // make the mine super aggressive -- Samual: Rather, make it not care if a team mate is near. + } + + // a player's mines shall explode if he disconnects or dies + // TODO: Do this on team change too -- Samual: But isn't a player killed when they switch teams? - if(!IS_PLAYER(self.realowner) || self.realowner.deadflag != DEAD_NO || self.realowner.freezetag_frozen) ++ if(!IS_PLAYER(self.realowner) || self.realowner.deadflag != DEAD_NO || self.realowner.frozen) + { + other = world; + self.projectiledeathtype |= HITTYPE_BOUNCE; + W_MineLayer_Explode(); + return; + } + + // set the mine for detonation when a foe gets close enough + head = findradius(self.origin, WEP_CVAR(minelayer, proximityradius)); + while(head) + { - if(IS_PLAYER(head) && head.deadflag == DEAD_NO && !head.freezetag_frozen) ++ if(IS_PLAYER(head) && head.deadflag == DEAD_NO && !head.frozen) + if(head != self.realowner && DIFF_TEAM(head, self.realowner)) // don't trigger for team mates + if(!self.mine_time) + { + spamsound(self, CH_SHOTS, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM); + self.mine_time = time + WEP_CVAR(minelayer, time); + } + head = head.chain; + } + + // explode if it's time to + if(self.mine_time && time >= self.mine_time) + { + W_MineLayer_ProximityExplode(); + return; + } + + // remote detonation + if(self.realowner.weapon == WEP_MINE_LAYER) + if(self.realowner.deadflag == DEAD_NO) + if(self.minelayer_detonate) + W_MineLayer_RemoteExplode(); +} + +void W_MineLayer_Touch(void) +{ + if(self.movetype == MOVETYPE_NONE || self.movetype == MOVETYPE_FOLLOW) + return; // we're already a stuck mine, why do we get called? TODO does this even happen? + + if(WarpZone_Projectile_Touch()) + { + if(wasfreed(self)) + self.realowner.minelayer_mines -= 1; + return; + } + + if(other && IS_PLAYER(other) && other.deadflag == DEAD_NO) + { + // hit a player + // don't stick + } + else + { + W_MineLayer_Stick(other); + } +} + +void W_MineLayer_Damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +{ + if(self.health <= 0) + return; + + float is_from_enemy = (inflictor.realowner != self.realowner); + + if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, (is_from_enemy ? 1 : -1))) + return; // g_projectiles_damage says to halt + + self.health = self.health - damage; + self.angles = vectoangles(self.velocity); + + if(self.health <= 0) + W_PrepareExplosionByDamage(attacker, W_MineLayer_Explode); +} + +void W_MineLayer_Attack(void) +{ + entity mine; + entity flash; + + // scan how many mines we placed, and return if we reached our limit + if(WEP_CVAR(minelayer, limit)) + { + if(self.minelayer_mines >= WEP_CVAR(minelayer, limit)) + { + // the refire delay keeps this message from being spammed + sprint(self, strcat("minelayer: You cannot place more than ^2", ftos(WEP_CVAR(minelayer, limit)), " ^7mines at a time\n") ); + play2(self, "weapons/unavailable.wav"); + return; + } + } + + W_DecreaseAmmo(WEP_CVAR(minelayer, ammo)); + + W_SetupShot_ProjectileSize(self, '-4 -4 -4', '4 4 4', FALSE, 5, "weapons/mine_fire.wav", CH_WEAPON_A, WEP_CVAR(minelayer, damage)); + pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + mine = WarpZone_RefSys_SpawnSameRefSys(self); + mine.owner = mine.realowner = self; + if(WEP_CVAR(minelayer, detonatedelay) >= 0) + mine.spawnshieldtime = time + WEP_CVAR(minelayer, detonatedelay); + else + mine.spawnshieldtime = -1; + mine.classname = "mine"; + mine.bot_dodge = TRUE; + mine.bot_dodgerating = WEP_CVAR(minelayer, damage) * 2; // * 2 because it can detonate inflight which makes it even more dangerous + + mine.takedamage = DAMAGE_YES; + mine.damageforcescale = WEP_CVAR(minelayer, damageforcescale); + mine.health = WEP_CVAR(minelayer, health); + mine.event_damage = W_MineLayer_Damage; + mine.damagedbycontents = TRUE; + + mine.movetype = MOVETYPE_TOSS; + PROJECTILE_MAKETRIGGER(mine); + mine.projectiledeathtype = WEP_MINE_LAYER; + setsize(mine, '-4 -4 -4', '4 4 4'); // give it some size so it can be shot + + setorigin(mine, w_shotorg - v_forward * 4); // move it back so it hits the wall at the right point + W_SetupProjVelocity_Basic(mine, WEP_CVAR(minelayer, speed), 0); + mine.angles = vectoangles(mine.velocity); + + mine.touch = W_MineLayer_Touch; + mine.think = W_MineLayer_Think; + mine.nextthink = time; + mine.cnt = time + (WEP_CVAR(minelayer, lifetime) - WEP_CVAR(minelayer, lifetime_countdown)); + mine.flags = FL_PROJECTILE; + mine.missile_flags = MIF_SPLASH | MIF_ARC | MIF_PROXY; + + CSQCProjectile(mine, TRUE, PROJECTILE_MINE, TRUE); + + // muzzle flash for 1st person view + flash = spawn(); + setmodel(flash, "models/flash.md3"); // precision set below + SUB_SetFade(flash, time, 0.1); + flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; + W_AttachToShotorg(flash, '5 0 0'); + + // common properties + + other = mine; MUTATOR_CALLHOOK(EditProjectile); + + self.minelayer_mines = W_MineLayer_Count(self); +} + +float W_MineLayer_PlacedMines(float detonate) +{ + entity mine; + float minfound = 0; + + for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.realowner == self) + { + if(detonate) + { + if(!mine.minelayer_detonate) + { + mine.minelayer_detonate = TRUE; + minfound = 1; + } + } + else + minfound = 1; + } + return minfound; +} + +float W_MineLayer(float req) +{ + entity mine; + float ammo_amount; + switch(req) + { + case WR_AIM: + { + // aim and decide to fire if appropriate + if(self.minelayer_mines >= WEP_CVAR(minelayer, limit)) + self.BUTTON_ATCK = FALSE; + else + self.BUTTON_ATCK = bot_aim(WEP_CVAR(minelayer, speed), 0, WEP_CVAR(minelayer, lifetime), FALSE); + if(skill >= 2) // skill 0 and 1 bots won't detonate mines! + { + // decide whether to detonate mines + entity targetlist, targ; + float edgedamage, coredamage, edgeradius, recipricoledgeradius, d; + float selfdamage, teamdamage, enemydamage; + edgedamage = WEP_CVAR(minelayer, edgedamage); + coredamage = WEP_CVAR(minelayer, damage); + edgeradius = WEP_CVAR(minelayer, radius); + recipricoledgeradius = 1 / edgeradius; + selfdamage = 0; + teamdamage = 0; + enemydamage = 0; + targetlist = findchainfloat(bot_attack, TRUE); + mine = find(world, classname, "mine"); + while(mine) + { + if(mine.realowner != self) + { + mine = find(mine, classname, "mine"); + continue; + } + targ = targetlist; + while(targ) + { + d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - mine.origin); + d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000); + // count potential damage according to type of target + if(targ == self) + selfdamage = selfdamage + d; + else if(targ.team == self.team && teamplay) + teamdamage = teamdamage + d; + else if(bot_shouldattack(targ)) + enemydamage = enemydamage + d; + targ = targ.chain; + } + mine = find(mine, classname, "mine"); + } + float desirabledamage; + desirabledamage = enemydamage; + if(time > self.invincible_finished && time > self.spawnshieldtime) + desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent; + if(teamplay && self.team) + desirabledamage = desirabledamage - teamdamage; + + mine = find(world, classname, "mine"); + while(mine) + { + if(mine.realowner != self) + { + mine = find(mine, classname, "mine"); + continue; + } + makevectors(mine.v_angle); + targ = targetlist; + if(skill > 9) // normal players only do this for the target they are tracking + { + targ = targetlist; + while(targ) + { + if( + (v_forward * normalize(mine.origin - targ.origin)< 0.1) + && desirabledamage > 0.1*coredamage + )self.BUTTON_ATCK2 = TRUE; + targ = targ.chain; + } + }else{ + float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000); + //As the distance gets larger, a correct detonation gets near imposible + //Bots are assumed to use the mine spawnfunc_light to see if the mine gets near a player + if(v_forward * normalize(mine.origin - self.enemy.origin)< 0.1) + if(IS_PLAYER(self.enemy)) + if(desirabledamage >= 0.1*coredamage) + if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1)) + self.BUTTON_ATCK2 = TRUE; + // dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n"); + } + + mine = find(mine, classname, "mine"); + } + // if we would be doing at X percent of the core damage, detonate it + // but don't fire a new shot at the same time! + if(desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events + self.BUTTON_ATCK2 = TRUE; + if((skill > 6.5) && (selfdamage > self.health)) + self.BUTTON_ATCK2 = FALSE; + //if(self.BUTTON_ATCK2 == TRUE) + // dprint(ftos(desirabledamage),"\n"); + if(self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE; + } + + return TRUE; + } + case WR_THINK: + { + if(autocvar_g_balance_minelayer_reload_ammo && self.clip_load < WEP_CVAR(minelayer, ammo)) // forced reload + { + // not if we're holding the minelayer without enough ammo, but can detonate existing mines + if(!(W_MineLayer_PlacedMines(FALSE) && self.WEP_AMMO(MINE_LAYER) < WEP_CVAR(minelayer, ammo))) + WEP_ACTION(self.weapon, WR_RELOAD); + } + else if(self.BUTTON_ATCK) + { + if(weapon_prepareattack(0, WEP_CVAR(minelayer, refire))) + { + W_MineLayer_Attack(); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(minelayer, animtime), w_ready); + } + } + + if(self.BUTTON_ATCK2) + { + if(W_MineLayer_PlacedMines(TRUE)) + sound(self, CH_WEAPON_B, "weapons/mine_det.wav", VOL_BASE, ATTN_NORM); + } + + return TRUE; + } + case WR_INIT: + { + precache_model("models/flash.md3"); + precache_model("models/mine.md3"); + precache_model("models/weapons/g_minelayer.md3"); + precache_model("models/weapons/v_minelayer.md3"); + precache_model("models/weapons/h_minelayer.iqm"); + precache_sound("weapons/mine_det.wav"); + precache_sound("weapons/mine_fire.wav"); + precache_sound("weapons/mine_stick.wav"); + precache_sound("weapons/mine_trigger.wav"); + MINELAYER_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) + return TRUE; + } + case WR_CHECKAMMO1: + { + // don't switch while placing a mine + if(ATTACK_FINISHED(self) <= time || self.weapon != WEP_MINE_LAYER) + { + ammo_amount = self.WEP_AMMO(MINE_LAYER) >= WEP_CVAR(minelayer, ammo); + ammo_amount += self.(weapon_load[WEP_MINE_LAYER]) >= WEP_CVAR(minelayer, ammo); + return ammo_amount; + } + return TRUE; + } + case WR_CHECKAMMO2: + { + if(W_MineLayer_PlacedMines(FALSE)) + return TRUE; + else + return FALSE; + } + case WR_CONFIG: + { + MINELAYER_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS) + return TRUE; + } + case WR_RESETPLAYER: + { + self.minelayer_mines = 0; + return TRUE; + } + case WR_RELOAD: + { + W_Reload(WEP_CVAR(minelayer, ammo), "weapons/reload.wav"); + return TRUE; + } + case WR_SUICIDEMESSAGE: + { + return WEAPON_MINELAYER_SUICIDE; + } + case WR_KILLMESSAGE: + { + return WEAPON_MINELAYER_MURDER; + } + } + return FALSE; +} +#endif +#ifdef CSQC +float W_MineLayer(float req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 12; + pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/mine_exp.wav", VOL_BASE, ATTN_NORM); + + return TRUE; + } + case WR_INIT: + { + precache_sound("weapons/mine_exp.wav"); + return TRUE; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return FALSE; + } + } + return FALSE; +} +#endif +#endif diff --cc qcsrc/server/cl_client.qc index c29523bfc,b3c6b6577..2167bdb55 --- a/qcsrc/server/cl_client.qc +++ b/qcsrc/server/cl_client.qc @@@ -1200,29 -1192,15 +1191,9 @@@ void ClientConnect (void if(!sv_foginterval && world.fog != "") stuffcmd(self, strcat("\nfog ", world.fog, "\nr_fog_exp2 0\nr_drawfog 1\n")); - if(autocvar_g_hitplots || strstrofs(strcat(" ", autocvar_g_hitplots_individuals, " "), strcat(" ", self.netaddress, " "), 0) >= 0) - { - self.hitplotfh = fopen(strcat("hits-", matchid, "-", self.netaddress, "-", ftos(self.playerid), ".plot"), FILE_WRITE); - fputs(self.hitplotfh, strcat("#name ", self.netname, "\n")); - } - else - self.hitplotfh = -1; + W_HitPlotOpen(self); - if(g_race || g_cts) { - string rr; - if(g_cts) - rr = CTS_RECORD; - else - rr = RACE_RECORD; - - msg_entity = self; - 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); - } - } - else if(autocvar_sv_teamnagger && !(autocvar_bot_vs_human && (c3==-1 && c4==-1)) && !g_ca) // teamnagger is currently bad for ca + if(autocvar_sv_teamnagger && !(autocvar_bot_vs_human && (c3==-1 && c4==-1)) && !g_ca && !g_cts && !g_race) // teamnagger is currently bad for ca, race & cts send_CSQC_teamnagger(); CheatInitClient(); @@@ -2355,12 -2375,9 +2365,12 @@@ void PlayerPreThink (void do_crouch = 0; if(self.vehicle) do_crouch = 0; - if(self.freezetag_frozen) + if(self.frozen) do_crouch = 0; - if(self.weapon == WEP_SHOTGUN && self.weaponentity.wframe == WFRAME_FIRE2 && time < self.weapon_nextthink) + + // WEAPONTODO: THIS SHIT NEEDS TO GO EVENTUALLY + // It cannot be predicted by the engine! + if((self.weapon == WEP_SHOCKWAVE || self.weapon == WEP_SHOTGUN) && self.weaponentity.wframe == WFRAME_FIRE2 && time < self.weapon_nextthink) do_crouch = 0; if (do_crouch) diff --cc qcsrc/server/mutators/base.qh index 4ee54f049,f4021eb4c..95d85d793 --- a/qcsrc/server/mutators/base.qh +++ b/qcsrc/server/mutators/base.qh @@@ -102,8 -103,13 +103,13 @@@ MUTATOR_HOOKABLE(SpectateCopy) MUTATOR_HOOKABLE(ForbidThrowCurrentWeapon); // returns 1 if throwing the current weapon shall not be allowed + MUTATOR_HOOKABLE(WeaponRateFactor); + // allows changing attack rate + // INPUT, OUTPUT: + float weapon_rate; + MUTATOR_HOOKABLE(SetStartItems); - // adjusts {warmup_}start_{items,weapons,ammo_{cells,rockets,nails,shells,fuel}} + // adjusts {warmup_}start_{items,weapons,ammo_{cells,plasma,rockets,nails,shells,fuel}} MUTATOR_HOOKABLE(BuildMutatorsString); // appends ":mutatorname" to ret_string for logging diff --cc qcsrc/server/mutators/mutator_nades.qc index 515d3901d,da75e25ac..70c4578bd --- a/qcsrc/server/mutators/mutator_nades.qc +++ b/qcsrc/server/mutators/mutator_nades.qc @@@ -38,41 -27,522 +27,520 @@@ void nade_spawn(entity _nade timer.owner = _nade; timer.skin = 10; - switch(_nade.realowner.team) + _nade.effects |= EF_LOWPRECISION; + + CSQCProjectile(_nade, TRUE, Nade_ProjectileFromID(_nade.nade_type, FALSE), TRUE); + } + + void napalm_damage(float dist, float damage, float edgedamage, float burntime) + { + entity e; + float d; + vector p; + + if ( damage < 0 ) + return; + + RandomSelection_Init(); + for(e = WarpZone_FindRadius(self.origin, dist, TRUE); e; e = e.chain) + if(e.takedamage == DAMAGE_AIM) + if(self.realowner != e || autocvar_g_nades_napalm_selfdamage) + if(!IS_PLAYER(e) || !self.realowner || DIFF_TEAM(e, self)) + if(!e.frozen) + { + p = e.origin; + p_x += e.mins_x + random() * (e.maxs_x - e.mins_x); + p_y += e.mins_y + random() * (e.maxs_y - e.mins_y); + p_z += e.mins_z + random() * (e.maxs_z - e.mins_z); + d = vlen(WarpZone_UnTransformOrigin(e, self.origin) - p); + if(d < dist) + { + e.fireball_impactvec = p; + RandomSelection_Add(e, 0, string_null, 1 / (1 + d), !Fire_IsBurning(e)); + } + } + if(RandomSelection_chosen_ent) + { + d = vlen(WarpZone_UnTransformOrigin(RandomSelection_chosen_ent, self.origin) - RandomSelection_chosen_ent.fireball_impactvec); + d = damage + (edgedamage - damage) * (d / dist); + Fire_AddDamage(RandomSelection_chosen_ent, self.realowner, d * burntime, burntime, self.projectiledeathtype | HITTYPE_BOUNCE); + //trailparticles(self, particleeffectnum("fireball_laser"), self.origin, RandomSelection_chosen_ent.fireball_impactvec); + pointparticles(particleeffectnum("fireball_laser"), self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1); + } + } + + + void napalm_ball_think() + { + if(round_handler_IsActive()) + if(!round_handler_IsRoundStarted()) + { + remove(self); + return; + } + + if(time > self.pushltime) + { + remove(self); + return; + } + + vector midpoint = ((self.absmin + self.absmax) * 0.5); + if(pointcontents(midpoint) == CONTENT_WATER) + { + self.velocity = self.velocity * 0.5; + + if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER) + { self.velocity_z = 200; } + } + + self.angles = vectoangles(self.velocity); + + napalm_damage(autocvar_g_nades_napalm_ball_radius,autocvar_g_nades_napalm_ball_damage, + autocvar_g_nades_napalm_ball_damage,autocvar_g_nades_napalm_burntime); + + self.nextthink = time + 0.1; + } + + + void nade_napalm_ball() + { + entity proj; + vector kick; + + spamsound(self, CH_SHOTS, "weapons/fireball_fire.wav", VOL_BASE, ATTEN_NORM); + + proj = spawn (); + proj.owner = self.owner; + proj.realowner = self.realowner; + proj.team = self.owner.team; + proj.classname = "grenade"; + proj.bot_dodge = TRUE; + proj.bot_dodgerating = autocvar_g_nades_napalm_ball_damage; + proj.movetype = MOVETYPE_BOUNCE; + proj.projectiledeathtype = DEATH_NADE_NAPALM; + PROJECTILE_MAKETRIGGER(proj); + setmodel(proj, "null"); + proj.scale = 1;//0.5; + setsize(proj, '-4 -4 -4', '4 4 4'); + setorigin(proj, self.origin); + proj.think = napalm_ball_think; + proj.nextthink = time; + proj.damageforcescale = autocvar_g_nades_napalm_ball_damageforcescale; + proj.effects = EF_LOWPRECISION | EF_FLAME; + + kick_x =(random() - 0.5) * 2 * autocvar_g_nades_napalm_ball_spread; + kick_y = (random() - 0.5) * 2 * autocvar_g_nades_napalm_ball_spread; + kick_z = (random()/2+0.5) * autocvar_g_nades_napalm_ball_spread; + proj.velocity = kick; + + proj.pushltime = time + autocvar_g_nades_napalm_ball_lifetime; + + proj.angles = vectoangles(proj.velocity); + proj.flags = FL_PROJECTILE; + proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC; + + //CSQCProjectile(proj, TRUE, PROJECTILE_NAPALM_FIRE, TRUE); + } + + + void napalm_fountain_think() + { + + if(round_handler_IsActive()) + if(!round_handler_IsRoundStarted()) + { + remove(self); + return; + } + + if(time >= self.ltime) + { + remove(self); + return; + } + + vector midpoint = ((self.absmin + self.absmax) * 0.5); + if(pointcontents(midpoint) == CONTENT_WATER) + { + self.velocity = self.velocity * 0.5; + + if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER) + { self.velocity_z = 200; } + + UpdateCSQCProjectile(self); + } + + napalm_damage(autocvar_g_nades_napalm_fountain_radius, autocvar_g_nades_napalm_fountain_damage, + autocvar_g_nades_napalm_fountain_edgedamage, autocvar_g_nades_napalm_burntime); + + self.nextthink = time + 0.1; + if(time >= self.nade_special_time) + { + self.nade_special_time = time + autocvar_g_nades_napalm_fountain_delay; + nade_napalm_ball(); + } + } + + void nade_napalm_boom() + { + entity fountain; + local float c; + for (c = 0; c < autocvar_g_nades_napalm_ball_count; c ++) + nade_napalm_ball(); + + + fountain = spawn(); + fountain.owner = self.owner; + fountain.realowner = self.realowner; + fountain.origin = self.origin; + setorigin(fountain, fountain.origin); + fountain.think = napalm_fountain_think; + fountain.nextthink = time; + fountain.ltime = time + autocvar_g_nades_napalm_fountain_lifetime; + fountain.pushltime = fountain.ltime; + fountain.team = self.team; + fountain.movetype = MOVETYPE_TOSS; + fountain.projectiledeathtype = DEATH_NADE_NAPALM; + fountain.bot_dodge = TRUE; + fountain.bot_dodgerating = autocvar_g_nades_napalm_fountain_damage; + fountain.nade_special_time = time; + setsize(fountain, '-16 -16 -16', '16 16 16'); + CSQCProjectile(fountain, TRUE, PROJECTILE_NAPALM_FOUNTAIN, TRUE); + } + + void nade_ice_freeze(entity freezefield, entity frost_target, float freeze_time) + { + frost_target.frozen_by = freezefield.realowner; + pointparticles(particleeffectnum("electro_impact"), frost_target.origin, '0 0 0', 1); + Freeze(frost_target, 1/freeze_time, 3, FALSE); + if(frost_target.ballcarried) + if(g_keepaway) { ka_DropEvent(frost_target); } + else { DropBall(frost_target.ballcarried, frost_target.origin, frost_target.velocity);} + if(frost_target.flagcarried) { ctf_Handle_Throw(frost_target, world, DROP_THROW); } + if(frost_target.nade) { toss_nade(frost_target, '0 0 0', time + 0.05); } + + kh_Key_DropAll(frost_target, FALSE); + } + + void nade_ice_think() + { + + if(round_handler_IsActive()) + if(!round_handler_IsRoundStarted()) + { + remove(self); + return; + } + + if(time >= self.ltime) + { + if ( autocvar_g_nades_ice_explode ) + { + string expef; + switch(self.realowner.team) + { + case NUM_TEAM_1: expef = "nade_red_explode"; break; + case NUM_TEAM_2: expef = "nade_blue_explode"; break; + case NUM_TEAM_3: expef = "nade_yellow_explode"; break; + case NUM_TEAM_4: expef = "nade_pink_explode"; break; + default: expef = "nade_neutral_explode"; break; + } + pointparticles(particleeffectnum(expef), self.origin + '0 0 1', '0 0 0', 1); + sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); + + RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, - autocvar_g_nades_nade_radius, self, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy); ++ autocvar_g_nades_nade_radius, self, world, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy); + Damage_DamageInfo(self.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, + autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, self.projectiledeathtype, 0, self); + } + remove(self); + return; + } + + + self.nextthink = time+0.1; + + // gaussian + float randomr; + randomr = random(); + randomr = exp(-5*randomr*randomr)*autocvar_g_nades_nade_radius; + float randomw; + randomw = random()*M_PI*2; + vector randomp; + randomp_x = randomr*cos(randomw); + randomp_y = randomr*sin(randomw); + randomp_z = 1; + pointparticles(particleeffectnum("electro_muzzleflash"), self.origin + randomp, '0 0 0', 1); + + if(time >= self.nade_special_time) + { + self.nade_special_time = time+0.7; + + + pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1); + pointparticles(particleeffectnum("icefield"), self.origin, '0 0 0', 1); + } + + + float current_freeze_time = self.ltime - time - 0.1; + + entity e; + for(e = findradius(self.origin, autocvar_g_nades_nade_radius); e; e = e.chain) + if(e != self) + if(!autocvar_g_nades_ice_teamcheck || (DIFF_TEAM(e, self.realowner) || e == self.realowner)) + if(e.takedamage && e.deadflag == DEAD_NO) + if(e.health > 0) + if(!e.revival_time || ((time - e.revival_time) >= 1.5)) + if(!e.frozen) + if(current_freeze_time > 0) + nade_ice_freeze(self, e, current_freeze_time); + } + + void nade_ice_boom() + { + entity fountain; + fountain = spawn(); + fountain.owner = self.owner; + fountain.realowner = self.realowner; + fountain.origin = self.origin; + setorigin(fountain, fountain.origin); + fountain.think = nade_ice_think; + fountain.nextthink = time; + fountain.ltime = time + autocvar_g_nades_ice_freeze_time; + fountain.pushltime = fountain.wait = fountain.ltime; + fountain.team = self.team; + fountain.movetype = MOVETYPE_TOSS; + fountain.projectiledeathtype = DEATH_NADE_ICE; + fountain.bot_dodge = FALSE; + setsize(fountain, '-16 -16 -16', '16 16 16'); + fountain.nade_special_time = time+0.3; + fountain.angles = self.angles; + + if ( autocvar_g_nades_ice_explode ) + { + setmodel(fountain, "models/grenademodel.md3"); + entity timer = spawn(); + setmodel(timer, "models/ok_nade_counter/ok_nade_counter.md3"); + setattachment(timer, fountain, ""); + timer.classname = "nade_timer"; + timer.colormap = self.colormap; + timer.glowmod = self.glowmod; + timer.think = nade_timer_think; + timer.nextthink = time; + timer.wait = fountain.ltime; + timer.owner = fountain; + timer.skin = 10; + } + else + setmodel(fountain, "null"); + } + + void nade_translocate_boom() + { + if(self.realowner.vehicle) + return; + + vector locout = self.origin + '0 0 1' * (1 - self.realowner.mins_z - 24); + tracebox(locout, self.realowner.mins, self.realowner.maxs, locout, MOVE_NOMONSTERS, self.realowner); + locout = trace_endpos; + + makevectors(self.realowner.angles); + + entity oldself = self; + self = self.realowner; + MUTATOR_CALLHOOK(PortalTeleport); + self.realowner = self; + self = oldself; + + TeleportPlayer(self, self.realowner, locout, self.realowner.mangle, v_forward * vlen(self.realowner.velocity), '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER); + } + + void nade_spawn_boom() + { + entity spawnloc = spawn(); + setorigin(spawnloc, self.origin); + setsize(spawnloc, self.realowner.mins, self.realowner.maxs); + spawnloc.movetype = MOVETYPE_NONE; + spawnloc.solid = SOLID_NOT; + spawnloc.drawonlytoclient = self.realowner; + spawnloc.effects = EF_STARDUST; + spawnloc.cnt = autocvar_g_nades_spawn_count; + + if(self.realowner.nade_spawnloc) + { + remove(self.realowner.nade_spawnloc); + self.realowner.nade_spawnloc = world; + } + + self.realowner.nade_spawnloc = spawnloc; + } + + void nade_heal_think() + { + if(time >= self.ltime) + { + remove(self); + return; + } + + self.nextthink = time; + + if(time >= self.nade_special_time) + { + self.nade_special_time = time+0.25; + self.nade_show_particles = 1; + } + else + self.nade_show_particles = 0; + } + + void nade_heal_touch() + { + float maxhealth; + float health_factor; + if(IS_PLAYER(other) || (other.flags & FL_MONSTER)) + if(other.deadflag == DEAD_NO) + if(!other.frozen) + { + health_factor = autocvar_g_nades_heal_rate*frametime/2; + if ( other != self.realowner ) + { + if ( SAME_TEAM(other,self) ) + health_factor *= autocvar_g_nades_heal_friend; + else + health_factor *= autocvar_g_nades_heal_foe; + } + if ( health_factor > 0 ) + { + maxhealth = (other.flags & FL_MONSTER) ? other.max_health : g_pickup_healthmega_max; + if ( other.health < maxhealth ) + { + if ( self.nade_show_particles ) + pointparticles(particleeffectnum("healing_fx"), other.origin, '0 0 0', 1); + other.health = min(other.health+health_factor, maxhealth); + } + other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot); + } + else if ( health_factor < 0 ) + { + Damage(other,self,self.realowner,-health_factor,DEATH_NADE_HEAL,other.origin,'0 0 0'); + } + + } + + if ( IS_REAL_CLIENT(other) || (other.vehicle_flags & VHF_ISVEHICLE) ) { - case NUM_TEAM_1: p = PROJECTILE_NADE_RED; break; - case NUM_TEAM_2: p = PROJECTILE_NADE_BLUE; break; - case NUM_TEAM_3: p = PROJECTILE_NADE_YELLOW; break; - case NUM_TEAM_4: p = PROJECTILE_NADE_PINK; break; - default: p = PROJECTILE_NADE; break; + entity show_red = (other.vehicle_flags & VHF_ISVEHICLE) ? other.owner : other; + show_red.stat_healing_orb = time+0.1; + show_red.stat_healing_orb_alpha = 0.75 * (self.ltime - time) / self.healer_lifetime; } + } - CSQCProjectile(_nade, TRUE, p, TRUE); + void nade_heal_boom() + { + entity healer; + healer = spawn(); + healer.owner = self.owner; + healer.realowner = self.realowner; + setorigin(healer, self.origin); + healer.healer_lifetime = autocvar_g_nades_heal_time; // save the cvar + healer.ltime = time + healer.healer_lifetime; + healer.team = self.realowner.team; + healer.bot_dodge = FALSE; + healer.solid = SOLID_TRIGGER; + healer.touch = nade_heal_touch; + + setmodel(healer, "models/ctf/shield.md3"); + healer.healer_radius = autocvar_g_nades_nade_radius; + vector size = '1 1 1' * healer.healer_radius / 2; + setsize(healer,-size,size); + + Net_LinkEntity(healer, TRUE, 0, healer_send); + + healer.think = nade_heal_think; + healer.nextthink = time; + healer.SendFlags |= 1; + } + void nade_monster_boom() + { + entity e = spawnmonster(self.pokenade_type, 0, self.realowner, self.realowner, self.origin, FALSE, FALSE, 1); + + if(autocvar_g_nades_pokenade_monster_lifetime > 0) + e.monster_lifetime = time + autocvar_g_nades_pokenade_monster_lifetime; + e.monster_skill = MONSTER_SKILL_INSANE; } void nade_boom() { string expef; + float nade_blast = 1; - switch(self.realowner.team) + switch ( self.nade_type ) { - case NUM_TEAM_1: expef = "nade_red_explode"; break; - case NUM_TEAM_2: expef = "nade_blue_explode"; break; - case NUM_TEAM_3: expef = "nade_yellow_explode"; break; - case NUM_TEAM_4: expef = "nade_pink_explode"; break; - default: expef = "nade_explode"; break; + case NADE_TYPE_NAPALM: + nade_blast = autocvar_g_nades_napalm_blast; + expef = "explosion_medium"; + break; + case NADE_TYPE_ICE: + nade_blast = 0; + expef = "electro_combo"; // hookbomb_explode electro_combo bigplasma_impact + break; + case NADE_TYPE_TRANSLOCATE: + nade_blast = 0; + expef = ""; + break; + case NADE_TYPE_MONSTER: + case NADE_TYPE_SPAWN: + nade_blast = 0; + switch(self.realowner.team) + { + case NUM_TEAM_1: expef = "spawn_event_red"; break; + case NUM_TEAM_2: expef = "spawn_event_blue"; break; + case NUM_TEAM_3: expef = "spawn_event_yellow"; break; + case NUM_TEAM_4: expef = "spawn_event_pink"; break; + default: expef = "spawn_event_neutral"; break; + } + break; + case NADE_TYPE_HEAL: + nade_blast = 0; + expef = "spawn_event_red"; + break; + + default: + case NADE_TYPE_NORMAL: + switch(self.realowner.team) + { + case NUM_TEAM_1: expef = "nade_red_explode"; break; + case NUM_TEAM_2: expef = "nade_blue_explode"; break; + case NUM_TEAM_3: expef = "nade_yellow_explode"; break; + case NUM_TEAM_4: expef = "nade_pink_explode"; break; + default: expef = "nade_neutral_explode"; break; + } } - sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM); - sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); pointparticles(particleeffectnum(expef), self.origin + '0 0 1', '0 0 0', 1); - Damage_DamageInfo(self.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, self.projectiledeathtype, 0, self); + sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM); + sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); -- self.takedamage = DAMAGE_NO; - RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, - + if(nade_blast) + { + RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, - autocvar_g_nades_nade_radius, self, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy); + autocvar_g_nades_nade_radius, self, world, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy); + Damage_DamageInfo(self.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, self.projectiledeathtype, 0, self); + } + + switch ( self.nade_type ) + { + case NADE_TYPE_NAPALM: nade_napalm_boom(); break; + case NADE_TYPE_ICE: nade_ice_boom(); break; + case NADE_TYPE_TRANSLOCATE: nade_translocate_boom(); break; + case NADE_TYPE_SPAWN: nade_spawn_boom(); break; + case NADE_TYPE_HEAL: nade_heal_boom(); break; + case NADE_TYPE_MONSTER: nade_monster_boom(); break; + } remove(self); } @@@ -101,26 -572,29 +570,28 @@@ void nade_beep( void nade_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) { + if(self.nade_type == NADE_TYPE_TRANSLOCATE || self.nade_type == NADE_TYPE_SPAWN) + return; + - if(DEATH_ISWEAPON(deathtype, WEP_LASER)) + if(DEATH_ISWEAPON(deathtype, WEP_BLASTER)) return; - if(DEATH_ISWEAPON(deathtype, WEP_NEX) || DEATH_ISWEAPON(deathtype, WEP_MINSTANEX)) + if(DEATH_ISWEAPON(deathtype, WEP_VORTEX) || DEATH_ISWEAPON(deathtype, WEP_VAPORIZER)) { force *= 6; damage = self.max_health * 0.55; } - if(DEATH_ISWEAPON(deathtype, WEP_UZI)) + if(DEATH_ISWEAPON(deathtype, WEP_MACHINEGUN)) damage = self.max_health * 0.1; - if((DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE) || DEATH_ISWEAPON(deathtype, WEP_SHOTGUN)) && !(deathtype & HITTYPE_SECONDARY)) // WEAPONTODO - damage = self.max_health * 1.1; - - if((DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE) || DEATH_ISWEAPON(deathtype, WEP_SHOTGUN)) && (deathtype & HITTYPE_SECONDARY)) - if(DEATH_ISWEAPON(deathtype, WEP_SHOTGUN)) - if(deathtype & HITTYPE_SECONDARY) ++ if((DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE) || DEATH_ISWEAPON(deathtype, WEP_SHOTGUN)) && (deathtype & HITTYPE_SECONDARY)) // WEAPONTODO { damage = self.max_health * 0.1; - force *= 15; + force *= 10; } + else + damage = self.max_health * 1.1; self.velocity += force; diff --cc qcsrc/server/progs.src index 9198c2411,288b24775..9cc8c9954 --- a/qcsrc/server/progs.src +++ b/qcsrc/server/progs.src @@@ -11,11 -11,17 +11,14 @@@ sys-post.q ../warpzonelib/common.qh ../warpzonelib/util_server.qh ../warpzonelib/server.qh - ../common/constants.qh + ../common/stats.qh ../common/teams.qh ../common/util.qh + ../common/nades.qh + ../common/buffs.qh ../common/test.qh ../common/counting.qh -../common/items.qh -../common/explosion_equation.qh ../common/urllib.qh ../common/command/markup.qh ../common/command/rpn.qh @@@ -228,7 -209,15 +225,10 @@@ target_spawn.q func_breakable.qc target_music.qc -../common/items.qc - + ../common/nades.qc + ../common/buffs.qc + - -accuracy.qc ../csqcmodellib/sv_model.qc -csqcprojectile.qc playerdemo.qc diff --cc qcsrc/server/weapons/csqcprojectile.qc index 11daf4cc2,000000000..3dd93c058 mode 100644,000000..100644 --- a/qcsrc/server/weapons/csqcprojectile.qc +++ b/qcsrc/server/weapons/csqcprojectile.qc @@@ -1,112 -1,0 +1,114 @@@ +.float csqcprojectile_type; + +float CSQCProjectile_SendEntity(entity to, float sf) +{ + float ft, fr; + + // note: flag 0x08 = no trail please (teleport bit) + sf = sf & 0x0F; + + if(self.csqcprojectile_clientanimate) + sf |= 0x80; // client animated, not interpolated + + if(self.flags & FL_ONGROUND) + sf |= 0x40; + + ft = fr = 0; + if(self.fade_time != 0 || self.fade_rate != 0) + { + ft = (self.fade_time - time) / sys_frametime; + fr = (1 / self.fade_rate) / sys_frametime; + if(ft <= 255 && fr <= 255 && fr >= 1) + sf |= 0x20; + } + + if(self.gravity != 0) + sf |= 0x10; + + WriteByte(MSG_ENTITY, ENT_CLIENT_PROJECTILE); + WriteByte(MSG_ENTITY, sf); + + if(sf & 1) + { + WriteCoord(MSG_ENTITY, self.origin_x); + WriteCoord(MSG_ENTITY, self.origin_y); + WriteCoord(MSG_ENTITY, self.origin_z); + + if(sf & 0x80) + { + WriteCoord(MSG_ENTITY, self.velocity_x); + WriteCoord(MSG_ENTITY, self.velocity_y); + WriteCoord(MSG_ENTITY, self.velocity_z); + if(sf & 0x10) + WriteCoord(MSG_ENTITY, self.gravity); + } + + if(sf & 0x20) + { + WriteByte(MSG_ENTITY, ft); + WriteByte(MSG_ENTITY, fr); + } ++ ++ WriteByte(MSG_ENTITY, self.realowner.team); + } + + if(sf & 2) + WriteByte(MSG_ENTITY, self.csqcprojectile_type); // TODO maybe put this into sf? + + return 1; +} + +.vector csqcprojectile_oldorigin; +void CSQCProjectile_Check(entity e) +{ + if(e.csqcprojectile_clientanimate) + if(e.flags & FL_ONGROUND) + if(e.origin != e.csqcprojectile_oldorigin) + UpdateCSQCProjectile(e); + e.csqcprojectile_oldorigin = e.origin; +} + +void CSQCProjectile(entity e, float clientanimate, float type, float docull) +{ + Net_LinkEntity(e, docull, 0, CSQCProjectile_SendEntity); + + e.csqcprojectile_clientanimate = clientanimate; + + if(e.movetype == MOVETYPE_TOSS || e.movetype == MOVETYPE_BOUNCE) + { + if(e.gravity == 0) + e.gravity = 1; + } + else + e.gravity = 0; + + if(!sound_allowed(MSG_BROADCAST, e)) + type |= 0x80; + e.csqcprojectile_type = type; +} + +void UpdateCSQCProjectile(entity e) +{ + if(e.SendEntity == CSQCProjectile_SendEntity) + { + // send new origin data + e.SendFlags |= 0x01; + } +// FIXME HACK + else if(e.SendEntity == ItemSend) + { + ItemUpdate(e); + } +// END HACK +} + +void UpdateCSQCProjectileAfterTeleport(entity e) +{ + if(e.SendEntity == CSQCProjectile_SendEntity) + { + // send new origin data + e.SendFlags |= 0x01; + // mark as teleported + e.SendFlags |= 0x08; + } +} diff --cc qcsrc/server/weapons/weaponsystem.qc index 7c8ce3511,000000000..4eb6a7364 mode 100644,000000..100644 --- a/qcsrc/server/weapons/weaponsystem.qc +++ b/qcsrc/server/weapons/weaponsystem.qc @@@ -1,938 -1,0 +1,942 @@@ +/* +=========================================================================== + + CLIENT WEAPONSYSTEM CODE + Bring back W_Weaponframe + +=========================================================================== +*/ + +.float weapon_frametime; + +float W_WeaponRateFactor() +{ + float t; + t = 1.0 / g_weaponratefactor; + ++ weapon_rate = t; ++ MUTATOR_CALLHOOK(WeaponRateFactor); ++ t = weapon_rate; ++ + return t; +} + +// VorteX: static frame globals +const float WFRAME_DONTCHANGE = -1; +const float WFRAME_FIRE1 = 0; +const float WFRAME_FIRE2 = 1; +const float WFRAME_IDLE = 2; +const float WFRAME_RELOAD = 3; +.float wframe; + +void(float fr, float t, void() func) weapon_thinkf; + +float CL_Weaponentity_CustomizeEntityForClient() +{ + self.viewmodelforclient = self.owner; + if(IS_SPEC(other)) + if(other.enemy == self.owner) + self.viewmodelforclient = other; + return TRUE; +} + +/* + * supported formats: + * + * 1. simple animated model, muzzle flash handling on h_ model: + * h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation + * tags: + * shot = muzzle end (shot origin, also used for muzzle flashes) + * shell = casings ejection point (must be on the right hand side of the gun) + * weapon = attachment for v_tuba.md3 + * v_tuba.md3 - first and third person model + * g_tuba.md3 - pickup model + * + * 2. simple animated model, muzzle flash handling on v_ model: + * h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation + * tags: + * weapon = attachment for v_tuba.md3 + * v_tuba.md3 - first and third person model + * tags: + * shot = muzzle end (shot origin, also used for muzzle flashes) + * shell = casings ejection point (must be on the right hand side of the gun) + * g_tuba.md3 - pickup model + * + * 3. fully animated model, muzzle flash handling on h_ model: + * h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model + * tags: + * shot = muzzle end (shot origin, also used for muzzle flashes) + * shell = casings ejection point (must be on the right hand side of the gun) + * handle = corresponding to the origin of v_tuba.md3 (used for muzzle flashes) + * v_tuba.md3 - third person model + * g_tuba.md3 - pickup model + * + * 4. fully animated model, muzzle flash handling on v_ model: + * h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model + * tags: + * shot = muzzle end (shot origin) + * shell = casings ejection point (must be on the right hand side of the gun) + * v_tuba.md3 - third person model + * tags: + * shot = muzzle end (for muzzle flashes) + * g_tuba.md3 - pickup model + */ + +// writes: +// self.origin, self.angles +// self.weaponentity +// self.movedir, self.view_ofs +// attachment stuff +// anim stuff +// to free: +// call again with "" +// remove the ent +void CL_WeaponEntity_SetModel(string name) +{ + float v_shot_idx; + if (name != "") + { + // if there is a child entity, hide it until we're sure we use it + if (self.weaponentity) + self.weaponentity.model = ""; + setmodel(self, strcat("models/weapons/v_", name, ".md3")); // precision set below + v_shot_idx = gettagindex(self, "shot"); // used later + if(!v_shot_idx) + v_shot_idx = gettagindex(self, "tag_shot"); + + setmodel(self, strcat("models/weapons/h_", name, ".iqm")); // precision set below + // preset some defaults that work great for renamed zym files (which don't need an animinfo) + self.anim_fire1 = animfixfps(self, '0 1 0.01', '0 0 0'); + self.anim_fire2 = animfixfps(self, '1 1 0.01', '0 0 0'); + self.anim_idle = animfixfps(self, '2 1 0.01', '0 0 0'); + self.anim_reload = animfixfps(self, '3 1 0.01', '0 0 0'); + + // if we have a "weapon" tag, let's attach the v_ model to it ("invisible hand" style model) + // if we don't, this is a "real" animated model + if(gettagindex(self, "weapon")) + { + if (!self.weaponentity) + self.weaponentity = spawn(); + setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter + setattachment(self.weaponentity, self, "weapon"); + } + else if(gettagindex(self, "tag_weapon")) + { + if (!self.weaponentity) + self.weaponentity = spawn(); + setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter + setattachment(self.weaponentity, self, "tag_weapon"); + } + else + { + if(self.weaponentity) + remove(self.weaponentity); + self.weaponentity = world; + } + + setorigin(self,'0 0 0'); + self.angles = '0 0 0'; + self.frame = 0; + self.viewmodelforclient = world; + + float idx; + + if(v_shot_idx) // v_ model attached to invisible h_ model + { + self.movedir = gettaginfo(self.weaponentity, v_shot_idx); + } + else + { + idx = gettagindex(self, "shot"); + if(!idx) + idx = gettagindex(self, "tag_shot"); + if(idx) + self.movedir = gettaginfo(self, idx); + else + { + print("WARNING: weapon model ", self.model, " does not support the 'shot' tag, will display shots TOTALLY wrong\n"); + self.movedir = '0 0 0'; + } + } + + if(self.weaponentity) // v_ model attached to invisible h_ model + { + idx = gettagindex(self.weaponentity, "shell"); + if(!idx) + idx = gettagindex(self.weaponentity, "tag_shell"); + if(idx) + self.spawnorigin = gettaginfo(self.weaponentity, idx); + } + else + idx = 0; + if(!idx) + { + idx = gettagindex(self, "shell"); + if(!idx) + idx = gettagindex(self, "tag_shell"); + if(idx) + self.spawnorigin = gettaginfo(self, idx); + else + { + print("WARNING: weapon model ", self.model, " does not support the 'shell' tag, will display casings wrong\n"); + self.spawnorigin = self.movedir; + } + } + + if(v_shot_idx) + { + self.oldorigin = '0 0 0'; // use regular attachment + } + else + { + if(self.weaponentity) + { + idx = gettagindex(self, "weapon"); + if(!idx) + idx = gettagindex(self, "tag_weapon"); + } + else + { + idx = gettagindex(self, "handle"); + if(!idx) + idx = gettagindex(self, "tag_handle"); + } + if(idx) + { + self.oldorigin = self.movedir - gettaginfo(self, idx); + } + else + { + print("WARNING: weapon model ", self.model, " does not support the 'handle' tag and neither does the v_ model support the 'shot' tag, will display muzzle flashes TOTALLY wrong\n"); + self.oldorigin = '0 0 0'; // there is no way to recover from this + } + } + + self.viewmodelforclient = self.owner; + } + else + { + self.model = ""; + if(self.weaponentity) + remove(self.weaponentity); + self.weaponentity = world; + self.movedir = '0 0 0'; + self.spawnorigin = '0 0 0'; + self.oldorigin = '0 0 0'; + self.anim_fire1 = '0 1 0.01'; + self.anim_fire2 = '0 1 0.01'; + self.anim_idle = '0 1 0.01'; + self.anim_reload = '0 1 0.01'; + } + + self.view_ofs = '0 0 0'; + + if(self.movedir_x >= 0) + { + vector v0; + v0 = self.movedir; + self.movedir = shotorg_adjust(v0, FALSE, FALSE); + self.view_ofs = shotorg_adjust(v0, FALSE, TRUE) - v0; + } + self.owner.stat_shotorg = compressShotOrigin(self.movedir); + self.movedir = decompressShotOrigin(self.owner.stat_shotorg); // make them match perfectly + + self.spawnorigin += self.view_ofs; // offset the casings origin by the same amount + + // check if an instant weapon switch occurred + setorigin(self, self.view_ofs); + // reset animstate now + self.wframe = WFRAME_IDLE; + setanim(self, self.anim_idle, TRUE, FALSE, TRUE); +} + +vector CL_Weapon_GetShotOrg(float wpn) +{ + entity wi, oldself; + vector ret; + wi = get_weaponinfo(wpn); + oldself = self; + self = spawn(); + CL_WeaponEntity_SetModel(wi.mdl); + ret = self.movedir; + CL_WeaponEntity_SetModel(""); + remove(self); + self = oldself; + return ret; +} + +void CL_Weaponentity_Think() +{ + float tb; + self.nextthink = time; + if (intermission_running) + self.frame = self.anim_idle_x; + if (self.owner.weaponentity != self) + { + if (self.weaponentity) + remove(self.weaponentity); + remove(self); + return; + } + if (self.owner.deadflag != DEAD_NO) + { + self.model = ""; + if (self.weaponentity) + self.weaponentity.model = ""; + return; + } + if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag) + { + self.weaponname = self.owner.weaponname; + self.dmg = self.owner.modelindex; + self.deadflag = self.owner.deadflag; + + CL_WeaponEntity_SetModel(self.owner.weaponname); + } + + tb = (self.effects & (EF_TELEPORT_BIT | EF_RESTARTANIM_BIT)); + self.effects = self.owner.effects & EFMASK_CHEAP; + self.effects &= ~EF_LOWPRECISION; + self.effects &= ~EF_FULLBRIGHT; // can mask team color, so get rid of it + self.effects &= ~EF_TELEPORT_BIT; + self.effects &= ~EF_RESTARTANIM_BIT; + self.effects |= tb; + + if(self.owner.alpha == default_player_alpha) + self.alpha = default_weapon_alpha; + else if(self.owner.alpha != 0) + self.alpha = self.owner.alpha; + else + self.alpha = 1; + + self.glowmod = self.owner.weaponentity_glowmod; + self.colormap = self.owner.colormap; + if (self.weaponentity) + { + self.weaponentity.effects = self.effects; + self.weaponentity.alpha = self.alpha; + self.weaponentity.colormap = self.colormap; + self.weaponentity.glowmod = self.glowmod; + } + + self.angles = '0 0 0'; + + float f = (self.owner.weapon_nextthink - time); + if (self.state == WS_RAISE && !intermission_running) + { + entity newwep = get_weaponinfo(self.owner.switchweapon); + f = f * g_weaponratefactor / max(f, newwep.switchdelay_raise); + self.angles_x = -90 * f * f; + } + else if (self.state == WS_DROP && !intermission_running) + { + entity oldwep = get_weaponinfo(self.owner.weapon); + f = 1 - f * g_weaponratefactor / max(f, oldwep.switchdelay_drop); + self.angles_x = -90 * f * f; + } + else if (self.state == WS_CLEAR) + { + f = 1; + self.angles_x = -90 * f * f; + } +} + +void CL_ExteriorWeaponentity_Think() +{ + float tag_found; + self.nextthink = time; + if (self.owner.exteriorweaponentity != self) + { + remove(self); + return; + } + if (self.owner.deadflag != DEAD_NO) + { + self.model = ""; + return; + } + if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag) + { + self.weaponname = self.owner.weaponname; + self.dmg = self.owner.modelindex; + self.deadflag = self.owner.deadflag; + if (self.owner.weaponname != "") + setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision set below + else + self.model = ""; + + if((tag_found = gettagindex(self.owner, "tag_weapon"))) + { + self.tag_index = tag_found; + self.tag_entity = self.owner; + } + else + setattachment(self, self.owner, "bip01 r hand"); + } + self.effects = self.owner.effects; + self.effects |= EF_LOWPRECISION; + self.effects = self.effects & EFMASK_CHEAP; // eat performance + if(self.owner.alpha == default_player_alpha) + self.alpha = default_weapon_alpha; + else if(self.owner.alpha != 0) + self.alpha = self.owner.alpha; + else + self.alpha = 1; + + self.glowmod = self.owner.weaponentity_glowmod; + self.colormap = self.owner.colormap; + + CSQCMODEL_AUTOUPDATE(); +} + +// spawning weaponentity for client +void CL_SpawnWeaponentity() +{ + self.weaponentity = spawn(); + self.weaponentity.classname = "weaponentity"; + self.weaponentity.solid = SOLID_NOT; + self.weaponentity.owner = self; + setmodel(self.weaponentity, ""); // precision set when changed + setorigin(self.weaponentity, '0 0 0'); + self.weaponentity.angles = '0 0 0'; + self.weaponentity.viewmodelforclient = self; + self.weaponentity.flags = 0; + self.weaponentity.think = CL_Weaponentity_Think; + self.weaponentity.customizeentityforclient = CL_Weaponentity_CustomizeEntityForClient; + self.weaponentity.nextthink = time; + + self.exteriorweaponentity = spawn(); + self.exteriorweaponentity.classname = "exteriorweaponentity"; + self.exteriorweaponentity.solid = SOLID_NOT; + self.exteriorweaponentity.exteriorweaponentity = self.exteriorweaponentity; + self.exteriorweaponentity.owner = self; + setorigin(self.exteriorweaponentity, '0 0 0'); + self.exteriorweaponentity.angles = '0 0 0'; + self.exteriorweaponentity.think = CL_ExteriorWeaponentity_Think; + self.exteriorweaponentity.nextthink = time; + + { + entity oldself = self; + self = self.exteriorweaponentity; + CSQCMODEL_AUTOINIT(); + self = oldself; + } +} + +// Weapon subs +void w_clear() +{ + if (self.weapon != -1) + { + self.weapon = 0; + self.switchingweapon = 0; + } + if (self.weaponentity) + { + self.weaponentity.state = WS_CLEAR; + self.weaponentity.effects = 0; + } +} + +void w_ready() +{ + if (self.weaponentity) + self.weaponentity.state = WS_READY; + weapon_thinkf(WFRAME_IDLE, 1000000, w_ready); +} + +.float prevdryfire; +.float prevwarntime; +float weapon_prepareattack_checkammo(float secondary) +{ + if (!(self.items & IT_UNLIMITED_WEAPON_AMMO)) + if (!WEP_ACTION(self.weapon, WR_CHECKAMMO1 + secondary)) + { + // always keep the Mine Layer if we placed mines, so that we can detonate them + entity mine; + if(self.weapon == WEP_MINE_LAYER) + for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self) + return FALSE; + + if(self.weapon == self.switchweapon && time - self.prevdryfire > 1) // only play once BEFORE starting to switch weapons + { + sound (self, CH_WEAPON_A, "weapons/dryfire.wav", VOL_BASE, ATTEN_NORM); + self.prevdryfire = time; + } + + if(WEP_ACTION(self.weapon, WR_CHECKAMMO2 - secondary)) // check if the other firing mode has enough ammo + { + if(time - self.prevwarntime > 1) + { + Send_Notification( + NOTIF_ONE, + self, + MSG_MULTI, + ITEM_WEAPON_PRIMORSEC, + self.weapon, + secondary, + (1 - secondary) + ); + } + self.prevwarntime = time; + } + else // this weapon is totally unable to fire, switch to another one + { + W_SwitchToOtherWeapon(self); + } + + return FALSE; + } + return TRUE; +} +.float race_penalty; +float weapon_prepareattack_check(float secondary, float attacktime) +{ + if(!weapon_prepareattack_checkammo(secondary)) + return FALSE; + + //if sv_ready_restart_after_countdown is set, don't allow the player to shoot + //if all players readied up and the countdown is running + if(time < game_starttime || time < self.race_penalty) { + return FALSE; + } + + if (timeout_status == TIMEOUT_ACTIVE) //don't allow the player to shoot while game is paused + return FALSE; + + // do not even think about shooting if switching + if(self.switchweapon != self.weapon) + return FALSE; + + if(attacktime >= 0) + { + // don't fire if previous attack is not finished + if (ATTACK_FINISHED(self) > time + self.weapon_frametime * 0.5) + return FALSE; + // don't fire while changing weapon + if (self.weaponentity.state != WS_READY) + return FALSE; + } + + return TRUE; +} +float weapon_prepareattack_do(float secondary, float attacktime) +{ + self.weaponentity.state = WS_INUSE; + + self.spawnshieldtime = min(self.spawnshieldtime, time); // kill spawn shield when you fire + + // if the weapon hasn't been firing continuously, reset the timer + if(attacktime >= 0) + { + if (ATTACK_FINISHED(self) < time - self.weapon_frametime * 1.5) + { + ATTACK_FINISHED(self) = time; + //dprint("resetting attack finished to ", ftos(time), "\n"); + } + ATTACK_FINISHED(self) = ATTACK_FINISHED(self) + attacktime * W_WeaponRateFactor(); + } + self.bulletcounter += 1; + //dprint("attack finished ", ftos(ATTACK_FINISHED(self)), "\n"); + return TRUE; +} +float weapon_prepareattack(float secondary, float attacktime) +{ + if(weapon_prepareattack_check(secondary, attacktime)) + { + weapon_prepareattack_do(secondary, attacktime); + return TRUE; + } + else + return FALSE; +} + +void weapon_thinkf(float fr, float t, void() func) +{ + vector a; + vector of, or, ou; + float restartanim; + + if(fr == WFRAME_DONTCHANGE) + { + fr = self.weaponentity.wframe; + restartanim = FALSE; + } + else if (fr == WFRAME_IDLE) + restartanim = FALSE; + else + restartanim = TRUE; + + of = v_forward; + or = v_right; + ou = v_up; + + if (self.weaponentity) + { + self.weaponentity.wframe = fr; + a = '0 0 0'; + if (fr == WFRAME_IDLE) + a = self.weaponentity.anim_idle; + else if (fr == WFRAME_FIRE1) + a = self.weaponentity.anim_fire1; + else if (fr == WFRAME_FIRE2) + a = self.weaponentity.anim_fire2; + else // if (fr == WFRAME_RELOAD) + a = self.weaponentity.anim_reload; + a_z *= g_weaponratefactor; + setanim(self.weaponentity, a, restartanim == FALSE, restartanim, restartanim); + } + + v_forward = of; + v_right = or; + v_up = ou; + + if(self.weapon_think == w_ready && func != w_ready && self.weaponentity.state == WS_RAISE) + { + backtrace("Tried to override initial weapon think function - should this really happen?"); + } + + t *= W_WeaponRateFactor(); + + // VorteX: haste can be added here + if (self.weapon_think == w_ready) + { + self.weapon_nextthink = time; + //dprint("started firing at ", ftos(time), "\n"); + } + if (self.weapon_nextthink < time - self.weapon_frametime * 1.5 || self.weapon_nextthink > time + self.weapon_frametime * 1.5) + { + self.weapon_nextthink = time; + //dprint("reset weapon animation timer at ", ftos(time), "\n"); + } + self.weapon_nextthink = self.weapon_nextthink + t; + self.weapon_think = func; + //dprint("next ", ftos(self.weapon_nextthink), "\n"); + + if((fr == WFRAME_FIRE1 || fr == WFRAME_FIRE2) && t) + { + if((self.weapon == WEP_SHOCKWAVE || self.weapon == WEP_SHOTGUN) && fr == WFRAME_FIRE2) + animdecide_setaction(self, ANIMACTION_MELEE, restartanim); + else + animdecide_setaction(self, ANIMACTION_SHOOT, restartanim); + } + else + { + if(self.anim_upper_action == ANIMACTION_SHOOT || self.anim_upper_action == ANIMACTION_MELEE) + self.anim_upper_action = 0; + } +} + +float forbidWeaponUse() +{ + if(time < game_starttime && !autocvar_sv_ready_restart_after_countdown) + return 1; + if(round_handler_IsActive() && !round_handler_IsRoundStarted()) + return 1; + if(self.player_blocked) + return 1; - if(self.freezetag_frozen) ++ if(self.frozen) + return 1; + return 0; +} + +void W_WeaponFrame() +{ + vector fo, ri, up; + + if (frametime) + self.weapon_frametime = frametime; + + if (!self.weaponentity || self.health < 1) + return; // Dead player can't use weapons and injure impulse commands + + if(forbidWeaponUse()) + if(self.weaponentity.state != WS_CLEAR) + { + w_ready(); + return; + } + + if(!self.switchweapon) + { + self.weapon = 0; + self.switchingweapon = 0; + self.weaponentity.state = WS_CLEAR; + self.weaponname = ""; + //self.items &= ~IT_AMMO; + return; + } + + makevectors(self.v_angle); + fo = v_forward; // save them in case the weapon think functions change it + ri = v_right; + up = v_up; + + // Change weapon + if (self.weapon != self.switchweapon) + { + if (self.weaponentity.state == WS_CLEAR) + { + // end switching! + self.switchingweapon = self.switchweapon; + entity newwep = get_weaponinfo(self.switchweapon); + + // the two weapon entities will notice this has changed and update their models + self.weapon = self.switchweapon; + self.weaponname = newwep.mdl; + self.bulletcounter = 0; + //self.ammo_field = newwep.ammo_field; + WEP_ACTION(self.switchweapon, WR_SETUP); + self.weaponentity.state = WS_RAISE; + + // set our clip load to the load of the weapon we switched to, if it's reloadable + if(newwep.spawnflags & WEP_FLAG_RELOADABLE && newwep.reloading_ammo) // prevent accessing undefined cvars + { + self.clip_load = self.(weapon_load[self.switchweapon]); + self.clip_size = newwep.reloading_ammo; + } + else + self.clip_load = self.clip_size = 0; + + weapon_thinkf(WFRAME_IDLE, newwep.switchdelay_raise, w_ready); + } + else if (self.weaponentity.state == WS_DROP) + { + // in dropping phase we can switch at any time + self.switchingweapon = self.switchweapon; + } + else if (self.weaponentity.state == WS_READY) + { + // start switching! + self.switchingweapon = self.switchweapon; + entity oldwep = get_weaponinfo(self.weapon); + + // set up weapon switch think in the future, and start drop anim + #ifndef INDEPENDENT_ATTACK_FINISHED + if(ATTACK_FINISHED(self) <= time + self.weapon_frametime * 0.5) + { + #endif + sound(self, CH_WEAPON_SINGLE, "weapons/weapon_switch.wav", VOL_BASE, ATTN_NORM); + self.weaponentity.state = WS_DROP; + weapon_thinkf(WFRAME_DONTCHANGE, oldwep.switchdelay_drop, w_clear); + #ifndef INDEPENDENT_ATTACK_FINISHED + } + #endif + } + } + + // LordHavoc: network timing test code + //if (self.button0) + // print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(self)), " >= ", ftos(self.weapon_nextthink), "\n"); + + float w; + w = self.weapon; + + // call the think code which may fire the weapon + // and do so multiple times to resolve framerate dependency issues if the + // server framerate is very low and the weapon fire rate very high + float c; + c = 0; + while (c < W_TICSPERFRAME) + { + c = c + 1; + if(w && !(self.weapons & WepSet_FromWeapon(w))) + { + if(self.weapon == self.switchweapon) + W_SwitchWeapon_Force(self, w_getbestweapon(self)); + w = 0; + } + + v_forward = fo; + v_right = ri; + v_up = up; + + if(w) + WEP_ACTION(self.weapon, WR_THINK); + else + WEP_ACTION(self.weapon, WR_GONETHINK); + + if (time + self.weapon_frametime * 0.5 >= self.weapon_nextthink) + { + if(self.weapon_think) + { + v_forward = fo; + v_right = ri; + v_up = up; + self.weapon_think(); + } + else + bprint("\{1}^1ERROR: undefined weapon think function for ", self.netname, "\n"); + } + } +} + +void W_AttachToShotorg(entity flash, vector offset) +{ + entity xflash; + flash.owner = self; + flash.angles_z = random() * 360; + + if(gettagindex(self.weaponentity, "shot")) + setattachment(flash, self.weaponentity, "shot"); + else + setattachment(flash, self.weaponentity, "tag_shot"); + setorigin(flash, offset); + + xflash = spawn(); + copyentity(flash, xflash); + + flash.viewmodelforclient = self; + + if(self.weaponentity.oldorigin_x > 0) + { + setattachment(xflash, self.exteriorweaponentity, ""); + setorigin(xflash, self.weaponentity.oldorigin + offset); + } + else + { + if(gettagindex(self.exteriorweaponentity, "shot")) + setattachment(xflash, self.exteriorweaponentity, "shot"); + else + setattachment(xflash, self.exteriorweaponentity, "tag_shot"); + setorigin(xflash, offset); + } +} + +void W_DecreaseAmmo(float ammo_use) +{ + entity wep = get_weaponinfo(self.weapon); + + if((self.items & IT_UNLIMITED_WEAPON_AMMO) && !wep.reloading_ammo) + return; + + // if this weapon is reloadable, decrease its load. Else decrease the player's ammo + if(wep.reloading_ammo) + { + self.clip_load -= ammo_use; + self.(weapon_load[self.weapon]) = self.clip_load; + } + else if(wep.ammo_field != ammo_none) + { + self.(wep.ammo_field) -= ammo_use; + if(self.(wep.ammo_field) < 0) + { + backtrace(sprintf( + "W_DecreaseAmmo(%.2f): '%s' subtracted too much %s from '%s', resulting with '%.2f' left... " + "Please notify Samual immediately with a copy of this backtrace!\n", + ammo_use, + wep.netname, + GetAmmoPicture(wep.ammo_field), + self.netname, + self.(wep.ammo_field) + )); + } + } +} + +// weapon reloading code + +.float reload_ammo_amount, reload_ammo_min, reload_time; +.float reload_complain; +.string reload_sound; + +void W_ReloadedAndReady() +{ + // finish the reloading process, and do the ammo transfer + + self.clip_load = self.old_clip_load; // restore the ammo counter, in case we still had ammo in the weapon before reloading + + // if the gun uses no ammo, max out weapon load, else decrease ammo as we increase weapon load + if(!self.reload_ammo_min || self.items & IT_UNLIMITED_WEAPON_AMMO || self.ammo_field == ammo_none) + self.clip_load = self.reload_ammo_amount; + else + { + while(self.clip_load < self.reload_ammo_amount && self.(self.ammo_field)) // make sure we don't add more ammo than we have + { + self.clip_load += 1; + self.(self.ammo_field) -= 1; + } + } + self.(weapon_load[self.weapon]) = self.clip_load; + + // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon, + // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there, + // so your weapon is disabled for a few seconds without reason + + //ATTACK_FINISHED(self) -= self.reload_time - 1; + + w_ready(); +} + +void W_Reload(float sent_ammo_min, string sent_sound) +{ + // set global values to work with + entity e; + e = get_weaponinfo(self.weapon); + + self.reload_ammo_min = sent_ammo_min; + self.reload_ammo_amount = e.reloading_ammo;; + self.reload_time = e.reloading_time; + self.reload_sound = sent_sound; + + // don't reload weapons that don't have the RELOADABLE flag + if (!(e.spawnflags & WEP_FLAG_RELOADABLE)) + { + dprint("Warning: Attempted to reload a weapon that does not have the WEP_FLAG_RELOADABLE flag. Fix your code!\n"); + return; + } + + // return if reloading is disabled for this weapon + if(!self.reload_ammo_amount) + return; + + // our weapon is fully loaded, no need to reload + if (self.clip_load >= self.reload_ammo_amount) + return; + + // no ammo, so nothing to load + if(self.ammo_field != ammo_none) + if(!self.(self.ammo_field) && self.reload_ammo_min) + if (!(self.items & IT_UNLIMITED_WEAPON_AMMO)) + { + if(IS_REAL_CLIENT(self) && self.reload_complain < time) + { + play2(self, "weapons/unavailable.wav"); + sprint(self, strcat("You don't have enough ammo to reload the ^2", WEP_NAME(self.weapon), "\n")); + self.reload_complain = time + 1; + } + // switch away if the amount of ammo is not enough to keep using this weapon + if (!(WEP_ACTION(self.weapon, WR_CHECKAMMO1) + WEP_ACTION(self.weapon, WR_CHECKAMMO2))) + { + self.clip_load = -1; // reload later + W_SwitchToOtherWeapon(self); + } + return; + } + + if (self.weaponentity) + { + if (self.weaponentity.wframe == WFRAME_RELOAD) + return; + + // allow switching away while reloading, but this will cause a new reload! + self.weaponentity.state = WS_READY; + } + + // now begin the reloading process + + sound(self, CH_WEAPON_SINGLE, self.reload_sound, VOL_BASE, ATTEN_NORM); + + // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon, + // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there, + // so your weapon is disabled for a few seconds without reason + + //ATTACK_FINISHED(self) = max(time, ATTACK_FINISHED(self)) + self.reload_time + 1; + + weapon_thinkf(WFRAME_RELOAD, self.reload_time, W_ReloadedAndReady); + + if(self.clip_load < 0) + self.clip_load = 0; + self.old_clip_load = self.clip_load; + self.clip_load = self.(weapon_load[self.weapon]) = -1; +}