pvp_revamped/init.lua

376 lines
15 KiB
Lua

local head_height = 1.2
local torso_height = 0.75
local block_duration = 100000
local disarm_chance_mul = 2
local block_duration_mul = 100000
local block_interval_mul = 0.15
local block_pool_mul = 2
local shield_pool_mul = 2
local block_wear_mul = 9000
local head_dmg_mul = 1.2
local torso_dmg_mul = 1.0
local arm_dmg_mul = 0.9
local leg_dmg_mul = 0.8
local front_dmg_mul = nil
local side_dmg_mul = 1.05
local back_dmg_mul = 1.1
local elevated_dmg_mul = 1.5
local equal_height_dmg_mul = nil
local lower_elevation_dmg_mul = 0.9
local velocity_dmg_mul = 0.15
local optimal_distance_dmg_mul = 0.2
local maximum_distance_dmg_mul = 0.1
local optimal_distance_mul = 0.5
local lag = 0
local dedicated_server_step = (tonumber(minetest.settings:get("dedicated_server_step")) or 0.1) * 1000000
local player_data = {}
local hit_points = {{x = 0.3, y = 1.2, z = 0, part = 1},
{x = 0, y = 1.2, z = 0, part = 0},
{x = -0.3, y = 1.2, z = 0, part = 1}}
local registered_tools = minetest.registered_tools
local raycast = minetest.raycast
local get_us_time = minetest.get_us_time
local get_player_by_name = minetest.get_player_by_name
local get_player_information = minetest.get_player_information
local add = vector.add
local multiply = vector.multiply
local subtract = vector.subtract
local distance = vector.distance
local cos = math.cos
local sin = math.sin
local abs = math.abs
local atan = math.atan
local random = math.random
local pi = math.pi
minetest.register_on_mods_loaded(function()
-- Get the max armor_use.
local max_armor_use
for k, v in pairs(registered_tools) do
if v.groups and v.groups.armor_use then
if not max_armor_use or max_armor_use < v.groups.armor_use then
max_armor_use = v.groups.armor_use
end
end
end
for k, v in pairs(registered_tools) do
if not (max_armor_use and v.groups and v.groups.armor_shield) and v.tool_capabilities and v.tool_capabilities.damage_groups.fleshy and v.tool_capabilities.full_punch_interval then
-- Block feature for tools with combat ability.
local tool_capabilities = v.tool_capabilities
local full_punch_interval = tool_capabilities.full_punch_interval
local punch_number = abs(tool_capabilities.damage_groups.fleshy - full_punch_interval)
local block_pool = punch_number * block_pool_mul
local full_block_interval = (full_punch_interval * block_interval_mul) * 1000000
local duration = block_duration + (punch_number * block_duration_mul)
local old_on_secondary_use = v.on_secondary_use
local old_on_place = v.on_place
if block_pool > 0 then
-- Allow the tool to block damage.
local block_action = function(user)
local time = get_us_time()
local name = user:get_player_name()
local data = player_data[name].block
-- Prevent spam blocking.
if not data or time - data.time > full_block_interval then
player_data[name].block = {pool = block_pool, time = time, duration = duration}
end
end
minetest.override_item(k, {on_secondary_use = function(itemstack, user, pointed_thing)
block_action(user)
return old_on_secondary_use(itemstack, user, pointed_thing)
end, on_place = function(itemstack, placer, pointed_thing)
block_action(placer)
return old_on_place(itemstack, placer, pointed_thing)
end})
end
elseif max_armor_use and v.groups and v.groups.armor_shield then
-- Block feature for shields.
local armor_heal = v.groups.armor_heal or 0
local armor_use = v.groups.armor_use or 0
local armor_shield = v.groups.armor_shield or 1
local old_on_secondary_use = v.on_secondary_use
local old_on_place = v.on_place
local fleshy = 1
if v.armor_groups and v.armor_groups.fleshy then
fleshy = v.armor_groups.fleshy
end
local block_pool = (max_armor_use - armor_use + armor_heal + armor_shield + fleshy) * shield_pool_mul
if block_pool > 0 then
-- Allow the shield to block damage.
local block_action = function(user)
player_data[user:get_player_name()].shield = {name = k, pool = block_pool}
end
minetest.override_item(k, {on_secondary_use = function(itemstack, user, pointed_thing)
block_action(user)
return old_on_secondary_use(itemstack, user, pointed_thing)
end, on_place = function(itemstack, placer, pointed_thing)
block_action(placer)
return old_on_place(itemstack, placer, pointed_thing)
end})
end
end
end
end)
-- Process player input data.
minetest.register_globalstep(function(dtime)
lag = dtime * 1000000
for k, v in pairs(player_data) do
if v.block then
local player = get_player_by_name(k)
local controls = player:get_player_control()
-- Check if a player is holding down the RMB key.
if controls.RMB then
-- Update the block time.
player_data[k].block.time = get_us_time()
end
end
end
end)
-- Create an empty data sheet for the player.
minetest.register_on_joinplayer(function(player)
player_data[player:get_player_name()] = {}
end)
-- Clear up memory if the player leaves.
minetest.register_on_leaveplayer(function(player)
player_data[player:get_player_name()] = nil
end)
-- Do the damage calculations when the player gets hit.
minetest.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, dir, damage)
local pos1 = hitter:get_pos()
local pos2 = player:get_pos()
local name = player:get_player_name()
local hitter_pos = {x = pos1.x, y = pos1.y, z = pos1.z}
local item = registered_tools[hitter:get_wielded_item():get_name()]
local range = 4
local yaw = player:get_look_horizontal()
local front
local full_punch
local arm
local re_yaw
local full_punch_interval = 1.4
-- Get whether this was a full punch.
if tool_capabilities and time_from_last_punch >= tool_capabilities.full_punch_interval then
full_punch = true
full_punch_interval = tool_capabilities.full_punch_interval
elseif tool_capabilities then
full_punch_interval = tool_capabilities.full_punch_interval
end
-- May remove.
if item and item.alt_range then
range = item.alt_range
elseif item and item.range then
range = item.range
end
-- Raise the position to eye height.
hitter_pos.y = hitter_pos.y + hitter:get_properties().eye_height
-- Get the second position from the direction of the hitter.
local _dir = hitter:get_look_dir()
local hit_pos1 = add(hitter_pos, _dir)
local hit_pos2 = add(hit_pos1, multiply(_dir, range))
local ray = raycast(hit_pos1, hit_pos2):next()
if ray then
local hit_point = ray.intersection_point
local newpos = subtract(hit_point, pos2)
local y1 = hit_point.y
local y2 = pos2.y
if head_height and head_dmg_mul and y1 > y2 + head_height then
-- If the player was hit in the head add extra damage.
damage = damage * head_dmg_mul
elseif torso_height and y1 > y2 + torso_height then
-- Find if the player was hit in the torso or arm.
local near_part = 0
local past_distance = -1
for _, point in pairs(hit_points) do
local x = point.x
local y = point.y
local z = point.z
local co = cos(yaw)
local si = sin(yaw)
local re_x = co * x - si * z
local re_z = si * x + co * z
local dist = distance(newpos, {x = re_x, y = y, z = re_z})
if dist < past_distance or past_distance == -1 then
past_distance = dist
near_part = point.part
end
end
if near_part == 1 then
-- Hit in the arm.
arm = true
if arm_dmg_mul then
damage = damage * arm_dmg_mul
end
elseif torso_dmg_mul then
-- Hit in the torso.
damage = damage * torso_dmg_mul
end
elseif leg_dmg_mul then
-- Hit in the leg.
damage = damage * leg_dmg_mul
end
local dist = distance(hitter_pos, pos2)
local optimal_range = range * optimal_distance_mul
local dist_rounded = dist + 0.5 - (dist + 0.5) % 1
-- Add or remove damage based on the distance.
-- Full punches are not affected by maximum distance.
if not full_punch and optimal_distance_mul and maximum_distance_dmg_mul and dist_rounded > optimal_range then
damage = damage - range * maximum_distance_dmg_mul
elseif optimal_distance_mul and optimal_distance_dmg_mul and dist_rounded < optimal_range then
damage = damage + optimal_range - dist_rounded * optimal_distance_dmg_mul
end
-- Get the yaw from both the player and intersection point.
local yaw2 = atan(newpos.z / newpos.x) + pi * 0.5
if hit_point.x >= pos2.x then
yaw2 = yaw2 + pi
end
re_yaw = yaw - yaw2
if re_yaw <= 0.7853982 and re_yaw >= -0.7853982 then
-- Hit on the front.
front = true
if front_dmg_mul then
damage = damage * front_dmg_mul
end
elseif side_dmg_mul and re_yaw <= -0.7853982 and re_yaw >= -2.356194 then
-- Hit on the left-side.
damage = damage * side_dmg_mul
elseif back_dmg_mul and re_yaw <= -2.356194 and re_yaw >= -3.926991 then
-- Hit on the back-side.
damage = damage * back_dmg_mul
elseif side_dmg_mul then
-- Hit on the right-side.
damage = damage * side_dmg_mul
end
end
if elevated_dmg_mul and pos1.y > pos2.y then
-- Give a damage bonus or drawback to the aggressor if they are above the victim.
damage = damage * elevated_dmg_mul
elseif lower_elevation_dmg_mul and pos1.y < pos2.y then
-- Give a damage bonus or drawback to the aggressor if they are below the victim.
damage = damage * lower_elevation_dmg_mul
elseif equal_height_dmg_mul then
-- Give a damage bonus or drawback to the aggressor if they are equal footing with the victim.
damage = damage * equal_height_dmg_mul
end
-- This damage bonus can only be used if this is a full interval punch.
if full_punch and velocity_dmg_mul then
local v1 = hitter:get_player_velocity()
local vv
if front then
-- Ignore the victim's speed if you hit them in the front.
vv = abs(v1.x) + abs(v1.y) + abs(v1.z)
else
-- Subtract the victim's velocity from the aggressor if they where not hit in the front.
local v2 = player:get_player_velocity()
vv = abs(v1.x) - abs(v2.x) + abs(v1.y) - abs(v2.y) + abs(v1.z) - abs(v2.z)
end
if vv > 0 then
-- Give a damage bonus to the aggressor based on how fast they are running.
damage = damage + vv * velocity_dmg_mul
end
end
-- If damage is at or below zero set it to a default value.
if damage <= 0 then
damage = 1
end
-- Remove the hitter's blocking data.
local hitter_name = hitter:get_player_name()
player_data[hitter_name].block = nil
player_data[hitter_name].shield = nil
local data_block = player_data[name].block
local hp = player:get_hp()
local wielded_item = player:get_wielded_item()
local item_name = wielded_item:get_name()
-- Process if the player is blocking with a tool or not.
if front and data_block and data_block.pool > 0 and data_block.time + data_block.duration + lag + dedicated_server_step + abs(get_player_information(hitter_name).avg_jitter - get_player_information(name).avg_jitter) * 1000000 > get_us_time() then
-- Block the damage and add it as wear to the tool.
wielded_item:add_wear(((damage - full_punch_interval) / 75) * block_wear_mul)
player:set_wielded_item(wielded_item)
data_block.pool = data_block.pool - damage
player_data[name].block = data_block
return true
elseif data_block then
-- Block attempt failed.
player_data[name].block = nil
end
local data_shield = player_data[name].shield
-- Process if the player is blocking with a shield or not.
if data_shield and data_shield.pool > 0 and data_shield.name == item_name and (front or (re_yaw and re_yaw <= 1.570796 and re_yaw >= -2.356194)) then
-- Block the damage and add it as wear to the tool.
wielded_item:add_wear(((damage - full_punch_interval) / 75) * block_wear_mul)
player:set_wielded_item(wielded_item)
data_shield.pool = data_shield.pool - damage
player_data[name].shield = data_shield
return true
elseif data_shield then
-- Shield block attempt failed.
player_data[name].shield = nil
end
-- Process if the player was hit in the arm.
if arm then
local item2 = registered_tools[item_name]
local chance
if item2 and not data_shield and not data_block and item2.tool_capabilities and item2.tool_capabilities.damage_groups.fleshy and item2.tool_capabilities.full_punch_interval then
-- Compute the chance to disarm by the victim's hp and tool stats.
chance = random(0, ((hp + (item2.tool_capabilities.damage_groups.fleshy - item2.tool_capabilities.full_punch_interval) * disarm_chance_mul) - damage) + 1)
elseif not item2 and not data_shield and not data_block then
-- Compute the chance to disarm by the victim's hp.
chance = random(0, (hp - damage) + 1)
end
-- Disarm the player if chance equals zero.
if chance and chance <= 0 then
local drop_item = wielded_item:take_item()
local obj = minetest.add_item(pos2, drop_item)
if obj then
obj:get_luaentity().collect = true
end
player:set_wielded_item(wielded_item)
end
end
-- Damage the player.
player:set_hp(hp - damage, "punch")
return true
end)