From: Mario Date: Sun, 17 Jun 2018 06:33:08 +0000 (+1000) Subject: Implement a proper Heal function for the Arc's ability, so specific entities can... X-Git-Tag: xonotic-v0.8.5~2022^2~11 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=8e9e12267e3281fc0b66c81b831a7f3a3fabe298;p=xonotic%2Fxonotic-data.pk3dir.git Implement a proper Heal function for the Arc's ability, so specific entities can handle the way they're healed --- diff --git a/qcsrc/common/gamemodes/gamemode/assault/assault.qc b/qcsrc/common/gamemodes/gamemode/assault/assault.qc index 97a7b4df2..8fb63a9ad 100644 --- a/qcsrc/common/gamemodes/gamemode/assault/assault.qc +++ b/qcsrc/common/gamemodes/gamemode/assault/assault.qc @@ -331,6 +331,21 @@ spawnfunc(target_objective_decrease) } // destructible walls that can be used to trigger target_objective_decrease +bool destructible_heal(entity targ, entity inflictor, float amount, float limit) +{ + float true_limit = ((limit) ? limit : targ.max_health); + if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit) + return false; + + GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit); + if(targ.sprite) + { + WaypointSprite_UpdateHealth(targ.sprite, GetResourceAmount(targ, RESOURCE_HEALTH)); + } + func_breakable_colormod(targ); + return true; +} + spawnfunc(func_breakable); spawnfunc(func_assault_destructible) { @@ -338,6 +353,7 @@ spawnfunc(func_assault_destructible) this.spawnflags = 3; this.classname = "func_assault_destructible"; + this.event_heal = destructible_heal; IL_PUSH(g_assault_destructibles, this); if(assault_attacker_team == NUM_TEAM_1) diff --git a/qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc b/qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc index 15aedc732..12475bb73 100644 --- a/qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc +++ b/qcsrc/common/gamemodes/gamemode/onslaught/sv_onslaught.qc @@ -447,6 +447,21 @@ void ons_ControlPoint_Icon_Damage(entity this, entity inflictor, entity attacker this.SendFlags |= CPSF_STATUS; } +bool ons_ControlPoint_Icon_Heal(entity targ, entity inflictor, float amount, float limit) +{ + float true_limit = ((limit) ? limit : targ.max_health); + if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit) + return false; + + GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit); + if(targ.owner.iscaptured) + WaypointSprite_UpdateHealth(targ.owner.sprite, GetResourceAmount(targ, RESOURCE_HEALTH)); + else + WaypointSprite_UpdateBuildFinished(targ.owner.sprite, time + (targ.max_health - GetResourceAmount(targ, RESOURCE_HEALTH)) / (targ.count / ONS_CP_THINKRATE)); + targ.SendFlags |= CPSF_STATUS; + return true; +} + void ons_ControlPoint_Icon_Think(entity this) { this.nextthink = time + ONS_CP_THINKRATE; @@ -584,6 +599,7 @@ void ons_ControlPoint_Icon_Spawn(entity cp, entity player) e.bot_attack = true; IL_PUSH(g_bot_targets, e); e.event_damage = ons_ControlPoint_Icon_Damage; + e.event_heal = ons_ControlPoint_Icon_Heal; e.team = player.team; e.colormap = 1024 + (e.team - 1) * 17; e.count = (e.max_health - GetResourceAmount(e, RESOURCE_HEALTH)) * ONS_CP_THINKRATE / autocvar_g_onslaught_cp_buildtime; // how long it takes to build @@ -910,6 +926,7 @@ void ons_GeneratorDamage(entity this, entity inflictor, entity attacker, float d this.isshielded = false; this.takedamage = DAMAGE_NO; // can't be hurt anymore this.event_damage = func_null; // won't do anything if hurt + this.event_heal = func_null; this.count = 0; // reset counter setthink(this, func_null); this.nextthink = 0; @@ -944,6 +961,20 @@ void ons_GeneratorDamage(entity this, entity inflictor, entity attacker, float d this.SendFlags |= GSF_STATUS; } +bool ons_GeneratorHeal(entity targ, entity inflictor, float amount, float limit) +{ + float true_limit = ((limit) ? limit : targ.max_health); + if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit) + return false; + + GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit); + WaypointSprite_UpdateHealth(targ.sprite, GetResourceAmount(targ, RESOURCE_HEALTH)); + targ.frame = 10 * bound(0, (1 - GetResourceAmount(targ, RESOURCE_HEALTH) / targ.max_health), 1); + targ.lasthealth = GetResourceAmount(targ, RESOURCE_HEALTH); + targ.SendFlags |= GSF_STATUS; + return true; +} + void ons_GeneratorThink(entity this) { this.nextthink = time + GEN_THINKRATE; @@ -978,6 +1009,7 @@ void ons_GeneratorReset(entity this) this.islinked = true; this.isshielded = true; this.event_damage = ons_GeneratorDamage; + this.event_heal = ons_GeneratorHeal; setthink(this, ons_GeneratorThink); this.nextthink = time + GEN_THINKRATE; @@ -1040,6 +1072,7 @@ void ons_GeneratorSetup(entity gen) // called when spawning a generator entity o gen.bot_attack = true; IL_PUSH(g_bot_targets, gen); gen.event_damage = ons_GeneratorDamage; + gen.event_heal = ons_GeneratorHeal; gen.reset = ons_GeneratorReset; setthink(gen, ons_GeneratorThink); gen.nextthink = time + GEN_THINKRATE; diff --git a/qcsrc/common/monsters/sv_monsters.qc b/qcsrc/common/monsters/sv_monsters.qc index 63f4418de..c208e779a 100644 --- a/qcsrc/common/monsters/sv_monsters.qc +++ b/qcsrc/common/monsters/sv_monsters.qc @@ -527,6 +527,7 @@ void Monster_Dead_Fade(entity this) this.pos2 = this.angles; } this.event_damage = func_null; + this.event_heal = func_null; this.takedamage = DAMAGE_NO; setorigin(this, this.pos1); this.angles = this.pos2; @@ -956,6 +957,7 @@ void Monster_Dead(entity this, entity attacker, float gibbed) _setmodel(this, this.mdl_dead); this.event_damage = ((gibbed) ? func_null : Monster_Dead_Damage); + this.event_heal = func_null; this.solid = SOLID_CORPSE; this.takedamage = DAMAGE_AIM; this.deadflag = DEAD_DEAD; @@ -1057,6 +1059,18 @@ void Monster_Damage(entity this, entity inflictor, entity attacker, float damage } } +bool Monster_Heal(entity targ, entity inflictor, float amount, float limit) +{ + float true_limit = ((limit) ? limit : targ.max_health); + if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit) + return false; + + GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit); + if(targ.sprite) + WaypointSprite_UpdateHealth(targ.sprite, GetResourceAmount(targ, RESOURCE_HEALTH)); + return true; +} + // don't check for enemies, just keep walking in a straight line void Monster_Move_2D(entity this, float mspeed, bool allow_jumpoff) { @@ -1353,6 +1367,7 @@ bool Monster_Spawn(entity this, bool check_appear, int mon_id) this.damagedbycontents = true; this.monsterid = mon_id; this.event_damage = Monster_Damage; + this.event_heal = Monster_Heal; settouch(this, Monster_Touch); this.use = Monster_Use; this.solid = SOLID_BBOX; diff --git a/qcsrc/common/vehicles/sv_vehicles.qc b/qcsrc/common/vehicles/sv_vehicles.qc index 6c34741cc..b0fef4c3f 100644 --- a/qcsrc/common/vehicles/sv_vehicles.qc +++ b/qcsrc/common/vehicles/sv_vehicles.qc @@ -727,6 +727,20 @@ void vehicles_damage(entity this, entity inflictor, entity attacker, float damag } } +bool vehicles_heal(entity targ, entity inflictor, float amount, float limit) +{ + float true_limit = ((limit) ? limit : targ.max_health); + //if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit) + if(targ.vehicle_health <= 0 || targ.vehicle_health >= true_limit) + return false; + + targ.vehicle_health = min(targ.vehicle_health + amount, true_limit); + //GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit); + //if(targ.owner) + //targ.owner.vehicle_health = (targ.vehicle_health / targ.max_health) * 100; + return true; +} + bool vehicles_crushable(entity e) { if(IS_PLAYER(e) && time >= e.vehicle_enter_delay) @@ -1005,6 +1019,7 @@ void vehicles_enter(entity pl, entity veh) setsize(pl, STAT(PL_MIN, pl), STAT(PL_MAX, pl)); veh.event_damage = vehicles_damage; + veh.event_heal = vehicles_heal; veh.nextthink = 0; pl.items &= ~IT_USING_JETPACK; pl.angles = veh.angles; @@ -1118,6 +1133,7 @@ void vehicles_spawn(entity this) this.owner = NULL; settouch(this, vehicles_touch); this.event_damage = vehicles_damage; + this.event_heal = vehicles_heal; this.reset = vehicles_reset; this.iscreature = true; this.teleportable = false; // no teleporting for vehicles, too buggy @@ -1230,6 +1246,7 @@ bool vehicle_initialize(entity this, Vehicle info, bool nodrop) this.vehicleid = info.vehicleid; this.PlayerPhysplug = info.PlayerPhysplug; this.event_damage = func_null; + this.event_heal = func_null; settouch(this, vehicles_touch); setthink(this, vehicles_spawn); this.nextthink = time; diff --git a/qcsrc/common/weapons/weapon/arc.qc b/qcsrc/common/weapons/weapon/arc.qc index d6604623d..c02b0daf5 100644 --- a/qcsrc/common/weapons/weapon/arc.qc +++ b/qcsrc/common/weapons/weapon/arc.qc @@ -410,7 +410,7 @@ void W_Arc_Beam_Think(entity this) beam_endpos = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_endpos); new_dir = WarpZone_TransformVelocity(WarpZone_trace_transform, new_dir); - float is_player = ( + bool is_player = ( IS_PLAYER(trace_ent) || trace_ent.classname == "body" @@ -418,129 +418,42 @@ void W_Arc_Beam_Think(entity this) IS_MONSTER(trace_ent) ); - // TODO: takedamage flag for things that can be healed? - if(trace_ent && (trace_ent.takedamage || trace_ent.classname == "onslaught_generator" || trace_ent.classname == "onslaught_controlpoint_icon") && (is_player || WEP_CVAR(arc, beam_nonplayerdamage))) + if(trace_ent) { - // 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) - ); - - // TODO: api or event for things that can be healed - if(IS_VEHICLE(trace_ent) && SAME_TEAM(own, trace_ent)) + if(SAME_TEAM(own, trace_ent)) { float roothealth = ((burst) ? WEP_CVAR(arc, burst_healing_hps) : WEP_CVAR(arc, beam_healing_hps)); - // not handling shield, since it's not exactly a defensive stat - - if(trace_ent.vehicle_health <= trace_ent.max_health && roothealth) + float rootarmor = ((burst) ? WEP_CVAR(arc, burst_healing_aps) : WEP_CVAR(arc, beam_healing_aps)); + float hplimit = ((IS_PLAYER(trace_ent)) ? WEP_CVAR(arc, beam_healing_hmax) : 0); + Heal(trace_ent, own, roothealth, hplimit); + if(IS_PLAYER(trace_ent) && rootarmor) { - trace_ent.vehicle_health = min( - trace_ent.vehicle_health + (roothealth * coefficient), - trace_ent.max_health - ); - if(trace_ent.owner) - trace_ent.owner.vehicle_health = (trace_ent.vehicle_health / trace_ent.max_health) * 100; - new_beam_type = ARC_BT_HEAL; - } - } - else if(trace_ent.classname == "onslaught_generator" && SAME_TEAM(own, trace_ent)) - { - float roothealth = ((burst) ? WEP_CVAR(arc, burst_healing_hps) : WEP_CVAR(arc, beam_healing_hps)); - if(roothealth) - { - if(GetResourceAmount(trace_ent, RESOURCE_HEALTH) <= trace_ent.max_health) + if(GetResourceAmount(trace_ent, RESOURCE_ARMOR) <= WEP_CVAR(arc, beam_healing_amax)) { - GiveResourceWithLimit(trace_ent, RESOURCE_HEALTH, (roothealth * coefficient), trace_ent.max_health); - WaypointSprite_UpdateHealth(trace_ent.sprite, GetResourceAmount(trace_ent, RESOURCE_HEALTH)); - trace_ent.frame = 10 * bound(0, (1 - GetResourceAmount(trace_ent, RESOURCE_HEALTH) / trace_ent.max_health), 1); - trace_ent.lasthealth = GetResourceAmount(trace_ent, RESOURCE_HEALTH); - trace_ent.SendFlags |= GSF_STATUS; - } - new_beam_type = ARC_BT_HEAL; - } - } - else if(trace_ent.classname == "onslaught_controlpoint_icon" && SAME_TEAM(own, trace_ent)) - { - float roothealth = ((burst) ? WEP_CVAR(arc, burst_healing_hps) : WEP_CVAR(arc, beam_healing_hps)); - if(roothealth) - { - if(GetResourceAmount(trace_ent, RESOURCE_HEALTH) <= trace_ent.max_health) - { - GiveResourceWithLimit(trace_ent, RESOURCE_HEALTH, (roothealth * coefficient), trace_ent.max_health); - if(trace_ent.owner.iscaptured) - WaypointSprite_UpdateHealth(trace_ent.owner.sprite, GetResourceAmount(trace_ent, RESOURCE_HEALTH)); - else - WaypointSprite_UpdateBuildFinished(trace_ent.owner.sprite, time + (trace_ent.max_health - GetResourceAmount(trace_ent, RESOURCE_HEALTH)) / (trace_ent.count / ONS_CP_THINKRATE)); - } - new_beam_type = ARC_BT_HEAL; - } - } - else if(trace_ent.classname == "func_assault_destructible" && SAME_TEAM(own, trace_ent)) - { - float roothealth = ((burst) ? WEP_CVAR(arc, burst_healing_hps) : WEP_CVAR(arc, beam_healing_hps)); - - if(roothealth) - { - if(GetResourceAmount(trace_ent, RESOURCE_HEALTH) <= trace_ent.max_health) - { - GiveResourceWithLimit(trace_ent, RESOURCE_HEALTH, (roothealth * coefficient), trace_ent.max_health); - if(trace_ent.sprite) - { - WaypointSprite_UpdateHealth(trace_ent.sprite, GetResourceAmount(trace_ent, RESOURCE_HEALTH)); - } - func_breakable_colormod(trace_ent); + GiveResourceWithLimit(trace_ent, RESOURCE_ARMOR, (rootarmor * coefficient), WEP_CVAR(arc, beam_healing_amax)); + trace_ent.pauserotarmor_finished = max( + trace_ent.pauserotarmor_finished, + time + autocvar_g_balance_pause_armor_rot + ); } - new_beam_type = ARC_BT_HEAL; - } - } - else if(is_player && SAME_TEAM(own, trace_ent)) - { - float roothealth, rootarmor; - float maxhp; - 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); - } - maxhp = ((IS_MONSTER(trace_ent)) ? trace_ent.max_health : WEP_CVAR(arc, beam_healing_hmax)); - - if(GetResourceAmount(trace_ent, RESOURCE_HEALTH) <= maxhp && roothealth) - { - GiveResourceWithLimit(trace_ent, RESOURCE_HEALTH, (roothealth * coefficient), maxhp); - } - if(GetResourceAmount(trace_ent, RESOURCE_ARMOR) <= WEP_CVAR(arc, beam_healing_amax) && rootarmor && !IS_MONSTER(trace_ent)) - { - GiveResourceWithLimit(trace_ent, RESOURCE_ARMOR, (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 + else if(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) + ); + float rootdamage; if(is_player) { diff --git a/qcsrc/server/client.qc b/qcsrc/server/client.qc index 0c740545c..42d72edf4 100644 --- a/qcsrc/server/client.qc +++ b/qcsrc/server/client.qc @@ -380,6 +380,7 @@ void PutObserverInServer(entity this) this.oldvelocity = this.velocity; this.fire_endtime = -1; this.event_damage = func_null; + this.event_heal = func_null; for(int slot = 0; slot < MAX_AXH; ++slot) { @@ -674,6 +675,7 @@ void PutPlayerInServer(entity this) STAT(HUD, this) = HUD_NORMAL; this.event_damage = PlayerDamage; + this.event_heal = PlayerHeal; if(!this.bot_attack) IL_PUSH(g_bot_targets, this); diff --git a/qcsrc/server/defs.qh b/qcsrc/server/defs.qh index 1db5dd0c5..4c23aaeb3 100644 --- a/qcsrc/server/defs.qh +++ b/qcsrc/server/defs.qh @@ -36,6 +36,8 @@ float server_is_dedicated; .void(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) event_damage; +.bool(entity targ, entity inflictor, float amount, float limit) event_heal; + //.string wad; //.string map; diff --git a/qcsrc/server/g_damage.qc b/qcsrc/server/g_damage.qc index bdbe77352..92a80969c 100644 --- a/qcsrc/server/g_damage.qc +++ b/qcsrc/server/g_damage.qc @@ -1061,6 +1061,19 @@ float RadiusDamage (entity inflictor, entity attacker, float coredamage, float e return RadiusDamageForSource (inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad, cantbe, mustbe, false, forceintensity, deathtype, weaponentity, directhitentity); } +bool Heal(entity targ, entity inflictor, float amount, float limit) +{ + if(game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR) || STAT(FROZEN, targ)) + return false; + + bool healed = false; + if(targ.event_heal) + healed = targ.event_heal(targ, inflictor, amount, limit); + // TODO: additional handling? what if the healing kills them? should this abort if healing would do so etc + // TODO: healing fx! + return healed; +} + float Fire_IsBurning(entity e) { return (time < e.fire_endtime); diff --git a/qcsrc/server/g_damage.qh b/qcsrc/server/g_damage.qh index 9ab817853..b0c44a719 100644 --- a/qcsrc/server/g_damage.qh +++ b/qcsrc/server/g_damage.qh @@ -96,6 +96,10 @@ float RadiusDamageForSource (entity inflictor, vector inflictororigin, vector in float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity); +// Calls .event_heal on the target so that they can handle healing themselves +// a limit of 0 should be handled by the entity as its max health (if applicable) +bool Heal(entity targ, entity inflictor, float amount, float limit); + .float fire_damagepersec; .float fire_endtime; .float fire_deathtype; diff --git a/qcsrc/server/player.qc b/qcsrc/server/player.qc index 4e39cf6b7..73c78e7db 100644 --- a/qcsrc/server/player.qc +++ b/qcsrc/server/player.qc @@ -74,6 +74,7 @@ void CopyBody(entity this, float keepvelocity) clone.effects = this.effects; clone.glowmod = this.glowmod; clone.event_damage = this.event_damage; + clone.event_heal = this.event_heal; clone.anim_state = this.anim_state; clone.anim_time = this.anim_time; clone.anim_lower_action = this.anim_lower_action; @@ -629,6 +630,7 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, // set damage function to corpse damage this.event_damage = PlayerCorpseDamage; + this.event_damage = func_null; // call the corpse damage function just in case it wants to gib this.event_damage(this, inflictor, attacker, excess, deathtype, weaponentity, hitloc, force); @@ -664,6 +666,15 @@ void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, } } +bool PlayerHeal(entity targ, entity inflictor, float amount, float limit) +{ + if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= limit) + return false; + + GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, limit); + return true; +} + bool MoveToTeam(entity client, int team_colour, int type) { int lockteams_backup = lockteams; // backup any team lock diff --git a/qcsrc/server/player.qh b/qcsrc/server/player.qh index 5e6642e04..8c9bac9b6 100644 --- a/qcsrc/server/player.qh +++ b/qcsrc/server/player.qh @@ -39,4 +39,6 @@ bool MoveToTeam(entity client, float team_colour, float type); void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force); +bool PlayerHeal(entity targ, entity inflictor, float amount, float limit); + int Say(entity source, float teamsay, entity privatesay, string msgin, float floodcontrol);