186 lines
7.9 KiB
GDScript
186 lines
7.9 KiB
GDScript
class_name CombatSystem
|
|
extends RefCounted
|
|
|
|
var game: Node3D
|
|
const BLAST_RADIUS := 8.0
|
|
const BLAST_DAMAGE := 110.0
|
|
|
|
func _init(owner_game: Node3D) -> void:
|
|
game = owner_game
|
|
|
|
func actions(p: Fighter) -> void:
|
|
if not game.is_host or not game.active or game.match_over or p.hp <= 0 or p.rope_active:
|
|
return
|
|
var cmd := p.input_data
|
|
var fire: bool = cmd.get("fire", false)
|
|
var pressed := fire and not p.trigger_held
|
|
p.trigger_held = fire
|
|
if cmd.get("ultimate", false): game.streaks.place(p)
|
|
cmd["ultimate"] = false
|
|
if cmd.get("shield", false): EnergyShield.activate(p)
|
|
cmd["shield"] = false
|
|
if cmd.get("melee", false):
|
|
melee(p)
|
|
cmd["melee"] = false
|
|
if p.melee_left > 0 or p.mantle_left > 0 or p.rope_active:
|
|
return
|
|
if cmd.get("switch", false):
|
|
p.weapon = 4 if p.weapon == p.primary else p.primary
|
|
p.aim_blend = 0
|
|
p.reload_left = 0
|
|
p.cooldown = maxf(p.cooldown, float(WeaponHandling.DATA[p.weapon].equip))
|
|
cmd["switch"] = false
|
|
if p.infinite_left <= 0 and (cmd.get("reload", false) or p.magazines[p.weapon] == 0) and p.reload_left <= 0 and p.magazines[p.weapon] < p.weapon_stats().mag and p.reserves[p.weapon] > 0:
|
|
p.reload_left = p.weapon_stats().reload
|
|
game.fx.rpc("reload", p.eye(), p.peer_id, 0)
|
|
cmd["reload"] = false
|
|
if cmd.get("grenade", false) and p.grenades > 0:
|
|
p.grenades -= 1
|
|
p.protection = 0
|
|
var dir := Arsenal.direction(p.yaw, p.pitch)
|
|
game.spawn_grenade(p.peer_id, p.eye() + dir * 0.6, dir * 17 + Vector3.UP * 4)
|
|
cmd["grenade"] = false
|
|
if cmd.get("flash", false) and p.flashes > 0:
|
|
p.flashes -= 1
|
|
p.protection = 0
|
|
var dir := Arsenal.direction(p.yaw, p.pitch)
|
|
game.spawn_grenade(p.peer_id, p.eye() + dir * 0.6, dir * 16 + Vector3.UP * 4, "flash")
|
|
cmd["flash"] = false
|
|
if fire and (not p.weapon_stats().get("semi", false) or pressed or p.bot) and p.cooldown <= 0 and p.reload_left <= 0:
|
|
if p.magazines[p.weapon] > 0:
|
|
shoot(p)
|
|
else:
|
|
cmd["reload"] = true
|
|
|
|
func shoot(p: Fighter) -> void:
|
|
p.radar_left = 0.0 if WeaponAttachments.clean(p.skin_designs.get(str(p.weapon), {}).get("attachments", {})).get("muzzle", 0) == 1 else 1.5
|
|
var w: Dictionary = p.weapon_stats()
|
|
p.shot_serial += 1
|
|
if p.infinite_left <= 0: p.magazines[p.weapon] -= 1
|
|
p.cooldown = w.rate
|
|
p.protection = 0
|
|
var hit_any := false
|
|
var head_any := false
|
|
for pellet in int(w.pellets):
|
|
var spread: float = w.spread
|
|
if p.aiming:
|
|
spread *= 0.025 if p.weapon == 3 else (0.85 if w.pellets > 1 else 0.2)
|
|
if p.velocity.length() > 4:
|
|
spread *= 1.6
|
|
var direction := Arsenal.direction(p.yaw + randf_range(-spread, spread), p.pitch + randf_range(-spread, spread))
|
|
var route := ShotOrigin.path(p, direction, w.range)
|
|
var start: Vector3 = route.start
|
|
var end: Vector3 = route.end
|
|
var hit: Dictionary = route.hit
|
|
if not route.blocked and not p.bot and p.peer_id != 1 and p.peer_id in game.multiplayer.get_peers() and not game.history.frames.is_empty():
|
|
var peer: ENetPacketPeer = game.multiplayer.multiplayer_peer.get_peer(p.peer_id)
|
|
if peer != null:
|
|
var latency := minf(0.2, peer.get_statistic(ENetPacketPeer.PEER_ROUND_TRIP_TIME) / 2000.0)
|
|
hit = game.history.rewind_hit(p, start, end, latency)
|
|
hit = EnergyShield.clip_ray(game, p, start, end, hit)
|
|
if not hit.is_empty():
|
|
end = hit.position
|
|
if pellet == 0:
|
|
game.impact.rpc(end)
|
|
if hit.collider is Fighter:
|
|
var target: Fighter = hit.collider
|
|
var relative_height: float = hit.position.y - hit.get("origin", target.global_position).y
|
|
var head := relative_height > (1.0 if target.crouched else 1.48)
|
|
var multiplier := 1.65 if head else (0.78 if relative_height < 0.7 else 1.0)
|
|
var distance := start.distance_to(end)
|
|
var falloff := lerpf(1, 0.55, clampf((distance - float(w.range) * 0.3) / (float(w.range) * 0.7), 0, 1))
|
|
if damage(target, p, float(w.damage) * multiplier * falloff, w.short, head, start):
|
|
hit_any = true
|
|
head_any = head_any or head
|
|
if pellet == 0:
|
|
game.muzzle_trace.rpc(p.peer_id, start, end)
|
|
game.shot_event.rpc(p.eye(), p.peer_id, p.weapon, hit_any and not p.bot, head_any)
|
|
if p.bot:
|
|
p.pitch += float(w.recoil) * 0.3
|
|
|
|
func damage(target: Fighter, source: Fighter, amount: float, weapon_name: String, head: bool, origin: Vector3 = Vector3.INF) -> bool:
|
|
if target.hp <= 0 or target.protection > 0:
|
|
return false
|
|
if target != source and not game.enemies(target, source):
|
|
return false
|
|
var incoming := origin if origin.is_finite() else source.eye()
|
|
if EnergyShield.blocks(target, incoming): return false
|
|
target.hp = maxf(0, target.hp - amount)
|
|
target.last_hurt = 0
|
|
if not target.bot and (target.peer_id == game.multiplayer.get_unique_id() or target.peer_id in game.multiplayer.get_peers()):
|
|
game.combat_notice.rpc_id(target.peer_id, "hurt", target.life, origin if origin.is_finite() else source.eye())
|
|
if target.hp <= 0:
|
|
target.deaths += 1
|
|
target.streak = 0
|
|
target.infinite_left = 0
|
|
target.recon_left = 0
|
|
target.shield_left = 0
|
|
game.objectives.death(target)
|
|
game.ammo_drops.drop(target)
|
|
target.respawn_left = 3
|
|
target.killer = source.nickname
|
|
target.killer_weapon = weapon_name
|
|
target.killer_id = source.peer_id if source != target else 0
|
|
target.shape.set_deferred("disabled", true)
|
|
if source != target:
|
|
source.kills += 1
|
|
game.streaks.earned(source)
|
|
if not source.bot and (source.peer_id == game.multiplayer.get_unique_id() or source.peer_id in game.multiplayer.get_peers()):
|
|
game.combat_notice.rpc_id(source.peer_id, "elimination", source.life, target.eye(), target.nickname)
|
|
if game.config.mode in ["TDM", "FFA"]: game.scores[source.team] += 1
|
|
game.kill_notice.rpc(source.nickname, target.nickname, weapon_name, head)
|
|
game.fx.rpc("death", target.eye(), target.peer_id, 0)
|
|
game.check_win()
|
|
return true
|
|
|
|
func explode(pos: Vector3, owner_id: int) -> void:
|
|
if not game.is_host or not game.active or game.match_over:
|
|
return
|
|
game.detonate.rpc(pos)
|
|
if not game.players.has(owner_id):
|
|
return
|
|
game.burn_zones.ignite(pos, owner_id)
|
|
var source: Fighter = game.players[owner_id]
|
|
for target: Fighter in game.players.values():
|
|
var distance := pos.distance_to(target.eye())
|
|
if distance < BLAST_RADIUS and game.visible_between(pos + Vector3.UP * 0.15, target.eye(), [target.get_rid()]):
|
|
damage(target, source, lerpf(BLAST_DAMAGE, 0, distance / BLAST_RADIUS), "FRAG", false, pos)
|
|
|
|
func flash(pos: Vector3) -> void:
|
|
if not game.is_host or not game.active or game.match_over: return
|
|
game.fx.rpc("flash", pos, 0, 0)
|
|
for target: Fighter in game.players.values():
|
|
var offset := pos - target.eye()
|
|
var distance := offset.length()
|
|
if target.hp <= 0 or EnergyShield.blocks(target, pos) or distance > 20 or not game.visible_between(pos, target.eye(), [target.get_rid()]): continue
|
|
var facing := Arsenal.direction(target.yaw, target.pitch).dot(offset.normalized())
|
|
var strength := (1 - distance / 20) * (1.0 if facing > 0.2 else 0.3)
|
|
target.flash_left = maxf(target.flash_left, strength * 4)
|
|
|
|
func melee(p: Fighter) -> void:
|
|
if not game.is_host or not game.active or game.match_over or p.hp <= 0 or p.melee_left > 0 or p.mantle_left > 0 or p.rope_active:
|
|
return
|
|
p.melee_left = 0.65
|
|
p.aiming = false
|
|
p.cooldown = maxf(p.cooldown, p.melee_left)
|
|
p.reload_left = 0
|
|
p.protection = 0
|
|
game.fx.rpc("melee", p.eye(), p.peer_id, 0)
|
|
var forward := Arsenal.direction(p.yaw, p.pitch)
|
|
var nearest: Fighter = null
|
|
var best := 2.1
|
|
for target: Fighter in game.players.values():
|
|
if target == p or target.hp <= 0 or not game.enemies(p, target):
|
|
continue
|
|
var point := target.global_position + Vector3.UP * (0.75 if target.crouched else 1.2)
|
|
var offset := point - p.eye()
|
|
if offset.length() >= best or forward.dot(offset.normalized()) < 0.65:
|
|
continue
|
|
var ray := PhysicsRayQueryParameters3D.create(p.eye(), point, 3, [p.get_rid()])
|
|
var hit := game.get_world_3d().direct_space_state.intersect_ray(ray)
|
|
if not hit.is_empty() and hit.collider == target:
|
|
nearest = target
|
|
best = offset.length()
|
|
if nearest != null and damage(nearest, p, 65, "MELEE", false) and not p.bot:
|
|
game.feedback.rpc_id(p.peer_id, false)
|