X-Git-Url: https://git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fcommon%2Fweapons%2Fweapon%2Felectro.qc;h=c2ec43e8c374bddbdb917343ce44f98b1a08a42e;hb=4d0ef7ff19b8272007e0034f1320e7de2010d14e;hp=259fef6d5e6c745af684bb77db8c1f2e117a08fb;hpb=f3c3e749b957f526846ed8011a91cd1e7fb9568c;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/common/weapons/weapon/electro.qc b/qcsrc/common/weapons/weapon/electro.qc index 259fef6d5..c2ec43e8c 100644 --- a/qcsrc/common/weapons/weapon/electro.qc +++ b/qcsrc/common/weapons/weapon/electro.qc @@ -1,6 +1,7 @@ #include "electro.qh" #ifdef SVQC +#include void W_Electro_TriggerCombo(vector org, float rad, entity own) { @@ -34,16 +35,10 @@ void W_Electro_TriggerCombo(vector org, float rad, entity own) setthink(e, W_Electro_ExplodeCombo); // delay combo chains, looks cooler - e.nextthink = - ( - time - + - (WEP_CVAR(electro, combo_speed) ? - (vlen(e.WarpZone_findradius_dist) / WEP_CVAR(electro, combo_speed)) - : - 0 - ) - ); + float delay = 0; + if (WEP_CVAR(electro, combo_speed)) + delay = vlen(e.WarpZone_findradius_dist) / WEP_CVAR(electro, combo_speed); + e.nextthink = time + delay; } e = e.chain; } @@ -54,6 +49,7 @@ void W_Electro_ExplodeCombo(entity this) W_Electro_TriggerCombo(this.origin, WEP_CVAR(electro, combo_comboradius), this.realowner); this.event_damage = func_null; + this.velocity = this.movedir; // particle fx and decals need .velocity RadiusDamage( this, @@ -83,6 +79,7 @@ void W_Electro_Explode(entity this, entity directhitentity) this.event_damage = func_null; this.takedamage = DAMAGE_NO; + this.velocity = this.movedir; // particle fx and decals need .velocity if(this.move_movetype == MOVETYPE_BOUNCE || this.classname == "electro_orb") // TODO: classname is more reliable anyway? { @@ -133,7 +130,7 @@ void W_Electro_TouchExplode(entity this, entity toucher) } -void sys_phys_update_single(entity this); +//void sys_phys_update_single(entity this); void W_Electro_Bolt_Think(entity this) { @@ -154,27 +151,39 @@ void W_Electro_Bolt_Think(entity this) { if(e.classname == "electro_orb") { - // change owner to whoever caused the combo explosion - e.realowner = this.realowner; - e.takedamage = DAMAGE_NO; - e.classname = "electro_orb_chain"; - - // now set the next one to trigger as well - setthink(e, W_Electro_ExplodeCombo); - - // delay combo chains, looks cooler - e.nextthink = - ( - time - + - (WEP_CVAR(electro, combo_speed) ? - (vlen(e.WarpZone_findradius_dist) / WEP_CVAR(electro, combo_speed)) - : - 0 - ) - ); - - ++found; + bool explode; + if (this.owner == e.owner) + { + explode = WEP_CVAR_PRI(electro, midaircombo_own); + } + else if (SAME_TEAM(this.owner, e.owner)) + { + explode = WEP_CVAR_PRI(electro, midaircombo_teammate); + } + else + { + explode = WEP_CVAR_PRI(electro, midaircombo_enemy); + } + + if (explode) + { + // change owner to whoever caused the combo explosion + e.realowner = this.realowner; + e.takedamage = DAMAGE_NO; + e.classname = "electro_orb_chain"; + + // Only first orb explosion uses midaircombo_speed, others use the normal combo_speed. + // This allows to avoid the delay on the first explosion which looks better + // (the bolt and orb should explode together because they interacted together) + // while keeping the chaining delay. + setthink(e, W_Electro_ExplodeCombo); + float delay = 0; + if (WEP_CVAR_PRI(electro, midaircombo_speed)) + delay = vlen(e.WarpZone_findradius_dist) / WEP_CVAR_PRI(electro, midaircombo_speed); + e.nextthink = time + delay; + + ++found; + } } e = e.chain; } @@ -208,7 +217,7 @@ void W_Electro_Attack_Bolt(Weapon thiswep, entity actor, .entity weaponentity) thiswep.m_id ); - Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir); proj = new(electro_bolt); proj.owner = proj.realowner = actor; @@ -241,6 +250,37 @@ void W_Electro_Attack_Bolt(Weapon thiswep, entity actor, .entity weaponentity) // proj.com_phys_vel = proj.velocity; } +void W_Electro_Orb_Follow_Think(entity this) +{ + if (time > this.death_time) + { + adaptor_think2use_hittype_splash(this); + return; + } + if (this.move_movetype == MOVETYPE_FOLLOW) + { + int lost = LostMovetypeFollow(this); + if (lost == 2) + { + // FIXME if player disconnected, it isn't possible to drop the orb at player's origin + // see comment in LostMovetypeFollow implementation + delete(this); + return; + } + if (lost) + { + // drop the orb at the corpse's location + PROJECTILE_MAKETRIGGER(this); + set_movetype(this, MOVETYPE_TOSS); + + setthink(this, adaptor_think2use_hittype_splash); + this.nextthink = this.death_time; + return; + } + } + this.nextthink = time; +} + void W_Electro_Orb_Stick(entity this, entity to) { entity newproj = spawn(); @@ -251,14 +291,17 @@ void W_Electro_Orb_Stick(entity this, entity to) newproj.owner = this.owner; newproj.realowner = this.realowner; - setsize(newproj, this.mins, this.maxs); setorigin(newproj, this.origin); setmodel(newproj, MDL_PROJECTILE_ELECTRO); + setsize(newproj, this.mins, this.maxs); newproj.angles = vectoangles(-trace_plane_normal); // face against the surface + newproj.traileffectnum = _particleeffectnum(EFFECT_TR_NEXUIZPLASMA.eent_eff_name); + + newproj.movedir = -trace_plane_normal; newproj.takedamage = this.takedamage; newproj.damageforcescale = this.damageforcescale; - SetResourceAmountExplicit(newproj, RESOURCE_HEALTH, GetResourceAmount(this, RESOURCE_HEALTH)); + SetResourceExplicit(newproj, RES_HEALTH, GetResource(this, RES_HEALTH)); newproj.event_damage = this.event_damage; newproj.spawnshieldtime = this.spawnshieldtime; newproj.damagedbycontents = true; @@ -269,24 +312,39 @@ void W_Electro_Orb_Stick(entity this, entity to) newproj.weaponentity_fld = this.weaponentity_fld; settouch(newproj, func_null); - setthink(newproj, getthink(this)); - newproj.nextthink = this.nextthink; + newproj.death_time = this.death_time; newproj.use = this.use; newproj.flags = this.flags; IL_PUSH(g_projectiles, newproj); IL_PUSH(g_bot_dodge, newproj); + // check if limits are enabled (we can tell by checking if the original orb is listed) and push it to the list if so + if(LimitedElectroBallRubbleList && IL_CONTAINS(LimitedElectroBallRubbleList, this)) + { + ReplaceOldListedChildRubble(LimitedElectroBallRubbleList, newproj, this); + } + delete(this); if(to) - SetMovetypeFollow(this, to); + { + SetMovetypeFollow(newproj, to); + + setthink(newproj, W_Electro_Orb_Follow_Think); + newproj.nextthink = time; + } + else + { + setthink(newproj, adaptor_think2use_hittype_splash); + newproj.nextthink = newproj.death_time; + } } void W_Electro_Orb_Touch(entity this, entity toucher) { PROJECTILE_TOUCH(this, toucher); - if(toucher.takedamage == DAMAGE_AIM) - { if(WEP_CVAR_SEC(electro, touchexplode)) { W_Electro_Explode(this, toucher); } } + if(toucher.takedamage == DAMAGE_AIM && WEP_CVAR_SEC(electro, touchexplode)) + { W_Electro_Explode(this, toucher); } else if(toucher.owner != this.owner && toucher.classname != this.classname) // don't stick to player's other projectiles! { //UpdateCSQCProjectile(this); @@ -300,7 +358,7 @@ void W_Electro_Orb_Touch(entity this, entity toucher) void W_Electro_Orb_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) { - if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0) + if(GetResource(this, RES_HEALTH) <= 0) return; // note: combos are usually triggered by W_Electro_TriggerCombo, not damage @@ -309,8 +367,8 @@ void W_Electro_Orb_Damage(entity this, entity inflictor, entity attacker, float if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, (is_combo ? 1 : -1))) return; // g_projectiles_damage says to halt - TakeResource(this, RESOURCE_HEALTH, damage); - if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0) + TakeResource(this, RES_HEALTH, damage); + if(GetResource(this, RES_HEALTH) <= 0) { this.takedamage = DAMAGE_NO; this.nextthink = time; @@ -320,17 +378,11 @@ void W_Electro_Orb_Damage(entity this, entity inflictor, entity attacker, float this.realowner = inflictor.realowner; this.classname = "electro_orb_chain"; setthink(this, W_Electro_ExplodeCombo); - this.nextthink = time + - ( - // bound the length, inflictor may be in a galaxy far far away (warpzones) - min( - WEP_CVAR(electro, combo_radius), - vlen(this.origin - inflictor.origin) - ) - / - // delay combo chains, looks cooler - WEP_CVAR(electro, combo_speed) - ); + // delay combo chains, looks cooler + // bound the length, inflictor may be in a galaxy far far away (warpzones) + float len = min(WEP_CVAR(electro, combo_radius), vlen(this.origin - inflictor.origin)); + float delay = len / WEP_CVAR(electro, combo_speed); + this.nextthink = time + delay; } else { @@ -359,7 +411,7 @@ void W_Electro_Attack_Orb(Weapon thiswep, entity actor, .entity weaponentity) w_shotdir = v_forward; // no TrueAim for grenades please - Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + W_MuzzleFlash(thiswep, actor, weaponentity, w_shotorg, w_shotdir); entity proj = new(electro_orb); proj.owner = proj.realowner = actor; @@ -368,6 +420,7 @@ void W_Electro_Attack_Orb(Weapon thiswep, entity actor, .entity weaponentity) proj.bot_dodge = true; proj.bot_dodgerating = WEP_CVAR_SEC(electro, damage); proj.nextthink = time + WEP_CVAR_SEC(electro, lifetime); + proj.death_time = time + WEP_CVAR_SEC(electro, lifetime); PROJECTILE_MAKETRIGGER(proj); proj.projectiledeathtype = thiswep.m_id | HITTYPE_SECONDARY; proj.weaponentity_fld = weaponentity; @@ -381,7 +434,7 @@ void W_Electro_Attack_Orb(Weapon thiswep, entity actor, .entity weaponentity) setsize(proj, '-4 -4 -4', '4 4 4'); proj.takedamage = DAMAGE_YES; proj.damageforcescale = WEP_CVAR_SEC(electro, damageforcescale); - SetResourceAmountExplicit(proj, RESOURCE_HEALTH, WEP_CVAR_SEC(electro, health)); + SetResourceExplicit(proj, RES_HEALTH, WEP_CVAR_SEC(electro, health)); proj.event_damage = W_Electro_Orb_Damage; proj.flags = FL_PROJECTILE; IL_PUSH(g_projectiles, proj); @@ -394,6 +447,14 @@ void W_Electro_Attack_Orb(Weapon thiswep, entity actor, .entity weaponentity) proj.bouncestop = WEP_CVAR_SEC(electro, bouncestop); proj.missile_flags = MIF_SPLASH | MIF_ARC; + if(WEP_CVAR_SEC(electro, limit) > 0) + { + if (!LimitedElectroBallRubbleList) + LimitedElectroBallRubbleList = IL_NEW(); + ListNewChildRubble(LimitedElectroBallRubbleList, proj); + LimitedChildrenRubble(LimitedElectroBallRubbleList, "electro_orb", WEP_CVAR_SEC(electro, limit), adaptor_think2use_hittype_splash, actor); + } + CSQCProjectile(proj, true, PROJECTILE_ELECTRO, false); // no culling, it has sound MUTATOR_CALLHOOK(EditProjectile, actor, proj); @@ -407,10 +468,10 @@ void W_Electro_CheckAttack(Weapon thiswep, entity actor, .entity weaponentity, i { W_Electro_Attack_Orb(thiswep, actor, weaponentity); actor.(weaponentity).electro_count -= 1; + actor.(weaponentity).electro_secondarytime = time; weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(electro, animtime), W_Electro_CheckAttack); return; } - // WEAPONTODO: when the player releases the button, cut down the length of refire2? w_ready(thiswep, actor, weaponentity, fire); } @@ -463,27 +524,28 @@ METHOD(Electro, wr_think, void(entity thiswep, entity actor, .entity weaponentit if(fire & 1) { + if(time >= actor.(weaponentity).electro_secondarytime + WEP_CVAR_SEC(electro, refire2) * W_WeaponRateFactor(actor)) if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(electro, refire))) { - W_Electro_Attack_Bolt(thiswep, actor, weaponentity); - weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(electro, animtime), w_ready); + W_Electro_Attack_Bolt(thiswep, actor, weaponentity); + weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(electro, animtime), w_ready); } } else if(fire & 2) { - if(time >= actor.(weaponentity).electro_secondarytime) - if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(electro, refire))) + if(time >= actor.(weaponentity).electro_secondarytime + WEP_CVAR_SEC(electro, refire) * W_WeaponRateFactor(actor)) + if(weapon_prepareattack(thiswep, actor, weaponentity, true, -1)) { W_Electro_Attack_Orb(thiswep, actor, weaponentity); actor.(weaponentity).electro_count = WEP_CVAR_SEC(electro, count); + actor.(weaponentity).electro_secondarytime = time; weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(electro, animtime), W_Electro_CheckAttack); - actor.(weaponentity).electro_secondarytime = time + WEP_CVAR_SEC(electro, refire2) * W_WeaponRateFactor(actor); } } } METHOD(Electro, wr_checkammo1, bool(entity thiswep, entity actor, .entity weaponentity)) { - float ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(electro, ammo); + float ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_PRI(electro, ammo); ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_PRI(electro, ammo); return ammo_amount; } @@ -492,24 +554,16 @@ METHOD(Electro, wr_checkammo2, bool(entity thiswep, entity actor, .entity weapon float ammo_amount; if(WEP_CVAR(electro, combo_safeammocheck)) // true if you can fire at least one secondary blob AND one primary shot after it, otherwise false. { - ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo); + ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo); ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(electro, ammo) + WEP_CVAR_PRI(electro, ammo); } else { - ammo_amount = GetResourceAmount(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(electro, ammo); + ammo_amount = GetResource(actor, thiswep.ammo_type) >= WEP_CVAR_SEC(electro, ammo); ammo_amount += actor.(weaponentity).(weapon_load[thiswep.m_id]) >= WEP_CVAR_SEC(electro, ammo); } return ammo_amount; } -METHOD(Electro, wr_resetplayer, void(entity thiswep, entity actor)) -{ - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - actor.(weaponentity).electro_secondarytime = time; - } -} METHOD(Electro, wr_reload, void(entity thiswep, entity actor, .entity weaponentity)) { W_Reload(actor, weaponentity, min(WEP_CVAR_PRI(electro, ammo), WEP_CVAR_SEC(electro, ammo)), SND_RELOAD);