646 lines
18 KiB
Lua

local function get_dist() end
local function draw_particles() end
local function weapon_left_click() end
local function weapon_right_click() end
local function weapon_zoom() end
local function weapon_reload() end
local function can_shoot() end
local function check_immunity() end
local function update_magazine() end
local function check_weapon_type_and_attack() end
local function after_damage() end
local function kill() end
function block_league.register_weapon(name, def)
-- usato per avere una dichiarazione pulita E al tempo stesso non dover
-- passare anche il nome in on_use (che lo richiede)
def.name = name
minetest.register_node(name, {
name = def.name,
description = def.description,
drawtype = def.mesh and "mesh" or "item",
mesh = def.mesh or nil,
tiles = def.tiles or nil,
wield_image = def.wield_image or nil,
wield_scale = def.wield_scale,
inventory_image = def.inventory_image,
weapon_type = def.weapon_type,
damage = def.damage,
weapon_range = def.weapon_range,
knockback = def.knockback,
fire_delay = def.fire_delay,
range = def.range and def.range or 0,
pierce = def.pierce,
decrease_damage_with_distance = def.decrease_damage_with_distance,
continuos_fire = def.continuos_fire,
sound_shoot = def.sound_shoot,
sound_reload = def.sound_reload,
bullet_trail = def.bullet_trail,
consume_bullets = def.consume_bullets,
magazine = def.magazine,
reload_time = def.reload_time,
zoom = def.zoom,
bullet = def.bullet and block_league.register_bullet(def.bullet, def.damage, def.bullet_trail) or nil,
-- LMB = first fire
on_use = function(itemstack, user, pointed_thing)
weapon_left_click(def, user, pointed_thing)
end,
-- RMB = secondary use
on_secondary_use = function(itemstack, user, pointed_thing)
weapon_right_click(def, user, pointed_thing)
end,
on_place = function(itemstack, user, pointed_thing)
weapon_right_click(def, user, pointed_thing)
end,
-- Q = reload
on_drop = function(itemstack, user, pointed_thing)
weapon_reload(user, def)
end
})
end
function block_league.shoot(weapon, player, pointed_thing)
if not can_shoot(player, weapon) then return end
check_immunity(player)
update_magazine(player, weapon)
local p_name = player:get_player_name()
if weapon.sound_shoot then
minetest.sound_play(weapon.sound_shoot, {to_player = p_name})
end
check_weapon_type_and_attack(player, weapon, pointed_thing)
return true
end
function block_league.shoot_hitscan(user, weapon, pointed_thing)
local dir = user:get_look_dir()
local pos = user:get_pos()
local pos_head = {x = pos.x, y = pos.y+1.475, z = pos.z}
local pointed_players = block_league.get_pointed_players(pos_head, dir, weapon.weapon_range, user, weapon.bullet_trail, weapon.pierce)
if pointed_players then
block_league.apply_damage(user, pointed_players, weapon, weapon.decrease_damage_with_distance)
end
end
function block_league.shoot_bullet(user, bullet, pointed_thing)
local pos = user:get_pos()
local pos_head = {x = pos.x, y = pos.y + user:get_properties().eye_height, z = pos.z}
local bullet_name = bullet.name .. '_entity'
local bullet = minetest.add_entity(pos_head, bullet_name, user:get_player_name())
local speed = bullet.speed
local dir = user:get_look_dir()
bullet:set_velocity({
x=(dir.x * speed),
y=(dir.y * speed),
z=(dir.z * speed),
})
local yaw = user:get_look_horizontal()
local pitch = user:get_look_vertical()
local rotation = ({x = -pitch, y = yaw, z = 0})
bullet:set_rotation(rotation)
end
function block_league.shoot_end(player, weapon)
local p_name = player:get_player_name()
local arena = arena_lib.get_arena_by_player(p_name)
local p_meta = player:get_meta()
p_meta:set_int("bl_is_shooting", 0)
minetest.after(0.5, function()
if not arena_lib.is_player_in_arena(p_name, "block_league")
or arena.players[p_name].energy == 0
or p_meta:get_int("bl_reloading") == 1
or p_meta:get_int("bl_is_shooting") == 1
or p_meta:get_int("bl_is_speed_locked") == 1
then return end
player:set_physics_override({ speed = block_league.SPEED })
end)
end
-- ritorna un array di giocatori con il numero di giocatori trovati a indice 1.
-- Se non trova giocatori diversi da se stesso ritorna nil
function block_league.get_pointed_players(head_pos, dir, range, user, particle, has_piercing)
local p1 = vector.add(head_pos, vector.multiply(dir, 0))
local p2 = vector.add(head_pos, vector.multiply(dir, range))
local ray = minetest.raycast(p1, p2, true, false)
local players = {}
-- check su ogni cosa attraversata dal raycast (p1 a p2)
for hit in ray do
-- se è un oggetto
if hit.type == "object" then
-- se è un giocatore
if hit.ref and hit.ref:is_player() then
-- e non è colui che spara
if hit.ref ~= user then
table.insert(players, hit.ref)
end
elseif hit.ref:get_luaentity() then
local entity = hit.ref:get_luaentity()
if entity.initial_properties ~= nil then
if entity.initial_properties.is_bullet or entity.initial_properties.is_grenade then
--distrugge sia il proiettile con cui collide che se stesso
entity.old_p_name = entity.p_name
entity.p_name = user:get_player_name()
entity:_destroy()
end
end
end
else
-- se è un nodo mi fermo, e ritorno l'array se > 0 (ovvero ha trovato giocatori)
if hit.type == "node" then
if #players > 0 then
if particle ~= nil then
if not has_piercing then
local impact_dist = get_dist(head_pos, players[1]:get_pos())
draw_particles(particle, dir, p1, range, impact_dist)
else
local impact_dist = get_dist(head_pos, hit.intersection_point)
draw_particles(particle, dir, p1, range, impact_dist)
end
end
return players
else
if particle ~= nil then
local impact_dist = get_dist(head_pos, hit.intersection_point)
draw_particles(particle, dir, p1, range, impact_dist)
end
return nil
end
end
end
end
-- se ho sparato a qualcuno senza incrociare blocchi
if #players > 0 then
if has_piercing then
if particle ~= nil then
draw_particles(particle, dir, p1, range, 120)
end
return players
else
if particle ~= nil then
local impact_dist = get_dist(head_pos, players[1]:get_pos())
draw_particles(particle, dir, p1, range, impact_dist)
end
return {players[1]}
end
else
if particle ~= nil then
draw_particles(particle, dir, p1, range, 120)
end
return nil
end
end
-- può avere uno o più target: formato ObjectRef
function block_league.apply_damage(user, targets, weapon, decrease_damage_with_distance, knockback_dir)
local damage = weapon.damage
local knockback = weapon.knockback
local p_name = user:get_player_name()
local arena = arena_lib.get_arena_by_player(p_name)
local killed_players = 0
if not arena or arena.in_queue or arena.in_loading or arena.in_celebration then return end
if type(targets) ~= "table" then
targets = {targets}
end
local remaining_HP
-- per ogni giocatore colpito
for _, target in pairs(targets) do
if target:get_hp() <= 0 then return end
if target:get_meta():get_int("bl_immunity") == 1 then return end
local t_name = target:get_player_name()
-- se player e target sono nella stessa squadra, annullo
if arena_lib.is_player_in_same_team(arena, p_name, t_name) then return end
-- eventuale knockback
if knockback > 0 and knockback_dir then
local knk= vector.multiply(knockback_dir,knockback)
target:add_player_velocity(knk)
end
if weapon.weapon_type == 1 and decrease_damage_with_distance then
local dist = get_dist(user:get_pos(), target:get_pos())
local damage = damage - (damage * dist / weapon.weapon_range)
remaining_HP = target:get_hp() - damage
else
remaining_HP = target:get_hp() - damage
end
-- applico il danno
target:set_hp(remaining_HP, {type = "set_hp", player_name = p_name})
-- se è ancora vivo, riproduco suono danno
if target:get_hp() > 0 then
minetest.sound_play("bl_hit", {
to_player = p_name,
max_hear_distance = 1,
})
-- sennò kaputt
else
kill(arena, weapon, p_name, target)
if t_name ~= p_name then
killed_players = killed_players +1
end
end
end
-- calcoli post-danno
after_damage(arena, p_name, killed_players)
end
function block_league.deactivate_zoom(player)
--TODO: rimuovere HUD zoom armi
player:set_fov(0, _, 0.1)
end
-- TEMP: gestione zoom al cambio d'arma. Servirebbe un on_wield_item/on_unwield_item su Minetest
minetest.register_globalstep(function(dtime)
for _, player in pairs(arena_lib.get_players_in_minigame("block_league", true)) do
if player:get_fov() == 11 and (player:get_wielded_item():get_name() ~= "block_league:pixelgun" or player:get_meta():get_int("bl_reloading") == 1) then
block_league.deactivate_zoom(player)
end
end
end)
----------------------------------------------
---------------FUNZIONI LOCALI----------------
----------------------------------------------
function get_dist(pos1, pos2)
local lenx = math.abs(pos1.x - pos2.x)
local leny = math.abs(pos1.y - pos2.y)
local lenz = math.abs(pos1.z - pos2.z)
local hypot = math.sqrt((lenx * lenx) + (lenz * lenz))
local dist = math.sqrt((hypot * hypot) + (leny * leny))
return dist
end
function draw_particles(particle, dir, origin, range, impact_dist)
minetest.add_particlespawner({
amount = particle.amount,
time = 0.3,
minpos = origin,
maxpos = origin,
minvel = vector.multiply(dir, range),
maxvel = vector.multiply(dir, range),
minexptime = impact_dist/(range * 1.5),
maxexptime = impact_dist/(range * 1.5),
size = 2,
collisiondetection = false,
vertical = false,
texture = particle.image
})
end
function weapon_left_click(weapon, player, pointed_thing)
if not block_league.shoot(weapon, player, pointed_thing) then return end
if player:get_meta():get_int("bl_is_speed_locked") == 0 then
player:set_physics_override({ speed = block_league.SPEED_LOW })
end
if weapon.type ~= 3 then
player:get_meta():set_int("bl_is_shooting", 1)
end
-- controls.register_on_release non funziona se un tasto viene premuto E rilasciato
-- sullo stesso step. Quindi, quando questo fallisce (perché il giocatore è stato
-- troppo veloce), interviene l'after seguente che ricontrolla lo stato del tasto sx
-- sullo step subito successivo. Il metadato bl_is_shooting è usato come sistema di
-- verifica (attivato quando si spara con successo e disattivato quando si rilascia).
-- Se il tasto sx è stato rilasciato ma bl_is_shooting è ancora 1, vuol dire che
-- register_on_release ha fallito e c'è bisogno di intervenire chiamando la funzione
-- di rilascio
minetest.after(0.1, function()
if not player:get_player_control().LMB and player:get_meta():get_int("bl_is_shooting") == 1 then
block_league.shoot_end(player, weapon)
end
end)
end
function weapon_right_click(weapon, player, pointed_thing)
if not weapon.on_right_click and not weapon.zoom then return end
local p_name = player:get_player_name()
local arena = arena_lib.get_arena_by_player(p_name)
if not arena or not arena.in_game or player:get_hp() <= 0 or arena.weapons_disabled then return end
if weapon.zoom then
weapon_zoom(weapon, player)
return end
local p_meta = player:get_meta()
----- gestione delay dell'arma -----
if p_meta:get_int("bl_weap_secondary_delay") == 1 or p_meta:get_int("bl_death_delay") == 1 then
return end
p_meta:set_int("bl_weap_secondary_delay", 1)
minetest.after(weapon.weap_secondary_delay, function()
if not arena_lib.is_player_in_arena(p_name, "block_league") then return end
p_meta:set_int("bl_weap_secondary_delay", 0)
end)
----- fine gestione delay -----
check_immunity(player)
if weapon.on_right_click then
weapon.on_right_click(arena, weapon, player, pointed_thing)
end
end
function weapon_zoom(weapon, player)
local p_meta = player:get_meta()
if p_meta:get_int("bl_reloading") == 1 or p_meta:get_int("bl_death_delay") == 1 then return end
if player:get_fov() ~= weapon.zoom.fov then
player:set_fov(weapon.zoom.fov, _, 0.1)
-- TODO: applica texture, riproduci suono
else
block_league.deactivate_zoom(player)
end
end
function weapon_reload(player, weapon)
local w_name = weapon.name
local p_name = player:get_player_name()
local p_meta = player:get_meta()
local arena = arena_lib.get_arena_by_player(p_name)
if not arena or not arena.in_game or player:get_hp() <= 0
or arena.weapons_disabled or weapon.weapon_type == 3 or not weapon.magazine
or weapon.magazine == 0 or p_meta:get_int("bl_reloading") == 1
or arena.players[p_name].weapons_magazine[w_name] == weapon.magazine
then return end
minetest.sound_play(weapon.sound_reload, {to_player = p_name})
p_meta:set_int("bl_reloading", 1)
-- rimuovo eventuale zoom
if weapon.zoom and player:get_fov() == weapon.zoom.fov then
block_league.deactivate_zoom(player)
end
if p_meta:get_int("bl_is_speed_locked") == 0 then
player:set_physics_override({ speed = block_league.SPEED_LOW })
end
block_league.weapons_hud_update(arena, p_name, w_name, true)
minetest.after(weapon.reload_time, function()
if not arena_lib.is_player_in_arena(p_name, "block_league") then return end
p_meta:set_int("bl_weap_delay", 0)
p_meta:set_int("bl_reloading", 0)
local vel = arena.players[p_name].energy > 0 and block_league.SPEED or block_league.SPEED_LOW
if p_meta:get_int("bl_is_speed_locked") == 0 then
player:set_physics_override({ speed = vel })
end
arena.players[p_name].weapons_magazine[w_name] = weapon.magazine
block_league.weapons_hud_update(arena, p_name, w_name)
end)
end
function can_shoot(player, weapon)
local p_name = player:get_player_name()
if not arena_lib.is_player_in_arena(p_name) then return end
local p_meta = player:get_meta()
local arena = arena_lib.get_arena_by_player(p_name)
local w_name = weapon.name
if player:get_hp() <= 0 or arena.weapons_disabled then return end
if weapon.magazine and weapon.magazine <= 0 then return end
----- gestione delay dell'arma -----
if p_meta:get_int("bl_weap_delay") == 1 or
p_meta:get_int("bl_death_delay") == 1 or
p_meta:get_int("bl_reloading") == 1 then
return false end
p_meta:set_int("bl_weap_delay", 1)
minetest.after(weapon.fire_delay, function()
if not arena_lib.is_player_in_arena(p_name, "block_league") then return end
if weapon.magazine and p_meta:get_int("bl_reloading") == 0 then
p_meta:set_int("bl_weap_delay", 0)
elseif not weapon.magazine then
p_meta:set_int("bl_weap_delay", 0)
end
end)
----- fine gestione delay -----
--[[ Per quando si avranno caricatori limitati
if weapon.consume_bullets then
if inv:contains_item("main", weapon.bullet) then
inv:remove_item("main", weapon.bullet)
block_league.weapons_hud_update(arena, p_name, w_name)
else
return false
end
end]]
return true
end
function check_immunity(player)
if player:get_meta():get_int("bl_immunity") == 1 then
player:get_meta():set_int("bl_immunity", 0)
end
end
function update_magazine(player, weapon)
if not weapon.magazine or weapon.magazine <= 0 then return end
local w_name = weapon.name
local p_name = player:get_player_name()
local p_meta = player:get_meta()
local arena = arena_lib.get_arena_by_player(p_name)
arena.players[p_name].weapons_magazine[w_name] = arena.players[p_name].weapons_magazine[w_name] - 1
-- automatically reload if the magazine is now empty
if arena.players[p_name].weapons_magazine[w_name] == 0 and p_meta:get_int("bl_reloading") == 0 then
weapon_reload(player, weapon)
else
block_league.weapons_hud_update(arena, p_name, w_name)
end
end
function check_weapon_type_and_attack(player, weapon, pointed_thing)
if weapon.weapon_type ~= 3 then
if weapon.weapon_type == 1 then
block_league.shoot_hitscan(player, weapon, pointed_thing)
elseif weapon.weapon_type == 2 then
block_league.shoot_bullet(player, weapon.bullet, pointed_thing)
end
else
if pointed_thing.type ~= "object" or not pointed_thing.ref:is_player() then return end
block_league.apply_damage(player, pointed_thing.ref, weapon, false, player:get_look_dir())
end
end
function after_damage(arena, p_name, killed_players)
-- eventuale achievement doppia/tripla uccisione
if killed_players > 1 then
if killed_players == 2 then
block_league.add_achievement(p_name, 1)
elseif killed_players >= 3 then
block_league.add_achievement(p_name, 2)
end
arena_lib.send_message_players_in_arena(arena, minetest.colorize("#eea160", p_name .. " ") .. minetest.colorize("#d7ded7", S("has killed @1 players in a row!", killed_players)))
end
end
function kill(arena, weapon, p_name, target)
-- riproduco suono morte
minetest.sound_play("bl_kill", {to_player = p_name})
local t_name = target:get_player_name()
if t_name ~= p_name then
-- informo dell'uccisione
block_league.HUD_kill_update(p_name, S("YOU'VE KILLED @1", t_name))
minetest.chat_send_player(t_name, minetest.colorize("#d7ded7", S("You've been killed by @1", minetest.colorize("#eea160", p_name))))
local p_stats = arena.players[p_name]
local team = arena.teams[arena.players[p_name].teamID]
-- aggiungo la kill
team.kills = team.kills +1
p_stats.kills = p_stats.kills +1
-- aggiorno HUD
block_league.info_panel_update(arena)
block_league.hud_log_update(arena, weapon.inventory_image, p_name, t_name)
-- se è DM e il cap è raggiunto, finisce match
if arena.mode == 2 then
block_league.scoreboard_update_score(arena)
local team = arena.teams[arena.players[p_name].teamID]
if team.kills == arena.score_cap then
local mod = arena_lib.get_mod_by_player(p_name)
arena_lib.load_celebration(mod, arena, {p_name})
end
end
else
block_league.HUD_kill_update(t_name, S("You've killed yourself"))
block_league.hud_log_update(arena, "bl_log_suicide.png", p_name, t_name)
end
end