123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906 |
- local vector_round = vector.round
- local vector_distance = vector.distance
- local math_random = math.random
- local function debug_print(msg)
- --minetest.chat_send_all(msg)
- end
- armor.dueling_players = armor.dueling_players or {}
- local dueling_players = armor.dueling_players
- local ACTIVE_DUEL_PUNCH = nil
- -- Query whether player is currently in a duel.
- function armor.player_in_duel(pname)
- if dueling_players[pname] then
- return true
- end
- end
- -- For best duels, these numbers should be the same.
- -- This is just a max size for an arena. You can make smaller, just don't mark
- -- the whole area as part of the arena. Minimum size is 1 city block.
- local PUBLIC_BED_DISTANCE = 256
- local OPPONENT_DISTANCE = 256
- local DUEL_MAX_RADIUS = 256
- armor.DUEL_MAX_RADIUS = DUEL_MAX_RADIUS
- local ENV_TIME_AFTER_PUNCH = 15 -- Time from last punch env damage is accounted.
- local SPAWN_SAFE_ZONE = 5
- local RESPAWN_TIME = 10
- local SHOUT_COLOR = core.get_color_escape_sequence("#ff2a00")
- local DUEL_MELEE_STRINGS = {
- "<loser> lost a duel.",
- "<loser> got owned.",
- "<winner> defeated <loser> in a duel.",
- "<loser> lost to <winner>.",
- "<loser> was beat by <winner> in combat!",
- "<winner> crushed <loser> in combat!",
- "<winner> crushed <loser> in a duel.",
- "<winner> totally owned <loser>.",
- "<winner> won a duel with <loser>.",
- "<winner> dealt out a whopping drubbing.",
- "<loser> got <l_himself> a severe drubbing.",
- "<winner> beat <loser> in honorable combat!",
- "<winner> bested <loser>.",
- "<loser> got a royal walloping from <winner>.",
- "<loser> got trashed in a duel by <winner>.",
- "<winner> trashed <loser> in a duel.",
- "<loser> got some major hurt from <winner>.",
- "<winner> gave out a royal swatting to <loser>.",
- "<loser> was killed by <winner>'s <w_weapon>.",
- }
- local DUEL_ARROW_STRINGS = {
- "<loser> didn't get out of the way of <winner>'s flying projectile.",
- "<loser> never saw it coming.",
- "<loser> faced down <winner>'s artillery and lost.",
- "<winner> used <loser> for ranged target practice.",
- "<loser> became a pincushion.",
- "<loser> HEARD <winner>'s incoming artillery. Didn't avoid it.",
- "<winner> is having fun with that <w_weapon>.",
- "<winner> has pulled out the big guns.",
- "<winner> is busy sniping with <w_his> <w_weapon>. Watch out!",
- "<winner> didn't have to get close to <loser> to make that kill.",
- }
- local DUEL_STOMP_STRINGS = {
- "<winner> stomped on <loser>'s head.",
- "<loser> was flattened.",
- "<loser> was flattened by <winner>.",
- "<loser> got a taste of jackboot.",
- "<winner> used <loser> to cushion <w_his> fall.",
- "<loser> got <l_himself> some pancaking by <winner>'s boots of steel.",
- "<winner> smashed <loser> into the earth.",
- "<winner> crushed <loser> from above.",
- "<winner> stomped <loser> into the ground.",
- "<winner> used <loser> to break <w_his> fall.",
- "<winner> used <loser> like a trampoline!",
- }
- local DUEL_SUICIDE_STRINGS = {
- "<loser> killed <l_himself>.",
- "<loser> got a taste of <l_his> own medicine.",
- "<loser> got on the wrong end of <l_his> own weapon.",
- "<loser> suicided.",
- "<loser> ended <l_himself>.",
- "<loser> won a fight with <l_himself>.",
- "<loser> died like a noob: harm self-inflicted.",
- "<loser> self-terminated.",
- "<loser> died: incompetence.",
- "<loser> perished: weapon misuse.",
- "<loser> died: couldn't take what <l_he> dished out.",
- "<loser> killed <l_himself> with <l_his> own <l_weapon>.",
- "<loser> used <l_his> <l_weapon> on <l_himself>. Idiot!",
- "<loser> did something stupid.",
- "<loser> won the Darwin award.",
- "<loser> held <l_his> <l_weapon> by the wrong end.",
- "<loser> hit <l_himself>: terrible aim.",
- }
- local function announce_begin(pname)
- local msg = "# Server: <" .. rename.gpn(pname) .. "> has agreed to participate in a duel!"
- minetest.chat_send_all(SHOUT_COLOR .. msg)
- chat_logging.log_server_message(msg)
- end
- local function announce_end(pname, reason)
- local reasonstr = "no reason"
- if reason == "jail" then
- reasonstr = "sent to jail"
- elseif reason == "death" then
- reasonstr = "perished ... unfortunately"
- elseif reason == "bounds" then
- reasonstr = "out of bounds"
- elseif reason == "far" then
- reasonstr = "too far away"
- elseif reason == "left" then
- reasonstr = "disjunction"
- end
- local sex = skins.get_gender_strings(pname)
- local msg = "# Server: <" .. rename.gpn(pname) .. "> has ended " .. sex.his .. " participation in a duel: " .. reasonstr .. "."
- minetest.chat_send_all(SHOUT_COLOR .. msg)
- chat_logging.log_server_message(msg)
- end
- -- Lets outside code query if this player is currently respawning (implies they're in a duel).
- function armor.is_duelist_respawning(pname)
- local data = dueling_players[pname]
- if not data then
- return
- end
- -- If the timer exists, they're respawning.
- if data.respawn_countdown then
- return true
- end
- end
- local function set_visible(player, visible)
- if visible then
- pova.remove_modifier(player, "nametag", "duelist:respawn_invis")
- pova.remove_modifier(player, "properties", "duelist:respawn_invis")
- else
- gauges.remove_hp_bar_for_player(player:get_player_name())
- -- Make them invisible.
- pova.set_modifier(player, "nametag",
- {color={a=0, r=0, g=0, b=0}, text=""}, "duelist:respawn_invis",
- {priority=1000})
- pova.set_modifier(player, "properties", {
- visual_size = {x=0, y=0},
- makes_footstep_sound = false,
- -- Cannot be zero-size because otherwise player would fall through cracks.
- --collisionbox = {0},
- --selectionbox = {0},
- collide_with_objects = false,
- is_visible = false,
- pointable = false,
- show_on_minimap = false,
- }, "duelist:respawn_invis", {priority=1000})
- end
- end
- function armor.dueling_hud_update(player, duel_data)
- player:hud_change(duel_data.hud[2], "text",
- "Participants: " .. #(armor.get_likely_opponents(player, duel_data.start_pos)))
- if duel_data.respawn_countdown then
- local text = "Respawn in: " .. duel_data.respawn_countdown
- player:hud_change(duel_data.hud[6], "text", text)
- elseif duel_data.out_of_bounds and duel_data.out_of_bounds > 0 then
- local text = "Return to the combat zone! (" .. (30 - duel_data.out_of_bounds) .. ")"
- player:hud_change(duel_data.hud[6], "text", text)
- else
- -- Hide this element.
- player:hud_change(duel_data.hud[6], "text", "")
- end
- end
- -- Check whether player is in bounds to duel, and end duel if necessary.
- function armor.check_bounds(pname)
- if dueling_players[pname] then
- local pref = minetest.get_player_by_name(pname)
- -- Player left the game unexpectedly.
- if not pref then
- dueling_players[pname] = nil
- announce_end(pname, "left")
- return
- end
- local player_pos = vector_round(pref:get_pos())
- local data = dueling_players[pname]
- local in_arena = (city_block:in_pvp_arena(player_pos) and
- minetest.test_protection(player_pos, ""))
- -- Respawn countdown timer.
- if data.respawn_countdown then
- if data.respawn_countdown > 0 then
- --pref:set_pos(data.respawn_pos)
- data.respawn_countdown = data.respawn_countdown - 1
- else
- data.respawn_countdown = nil
- set_visible(pref, true)
- minetest.chat_send_player(pname, "# Server: You respawned.")
- end
- end
- -- Disable respawn protection once player has moved out of respawn area.
- if data.no_respawn_protection == nil then
- if not armor.in_pvp_respawn_area(player_pos, data.start_pos) then
- debug_print('disabling respawn protection: ' .. pname .. ': player moved out of spawn')
- data.no_respawn_protection = true
- end
- end
- -- Arena distance checks.
- if vector_distance(data.start_pos, player_pos) > DUEL_MAX_RADIUS or not in_arena then
- if vector_distance(data.start_pos, player_pos) < (DUEL_MAX_RADIUS + 100) then
- -- Player is slightly out of bounds. Warn them to return.
- if data.out_of_bounds >= 30 then
- -- Player has been out of bounds for 30 seconds.
- armor.end_duel(pref, "bounds")
- return
- end
- data.out_of_bounds = data.out_of_bounds + 1
- else
- -- Player has completely left the duel area (teleport?) End duel immediately.
- armor.end_duel(pref, "far")
- return
- end
- elseif vector_distance(data.start_pos, player_pos) <= DUEL_MAX_RADIUS and in_arena then
- data.out_of_bounds = 0
- end
- -- HUD update.
- armor.dueling_hud_update(pref, data)
- -- Check again.
- minetest.after(1, function() armor.check_bounds(pname) end)
- end
- end
- -- Call this when a player begins to duel.
- function armor.add_dueling_player(player, duel_pos)
- local pname = player:get_player_name()
- if dueling_players[pname] then
- return
- end
- local yoff = 18
- local hud1 = player:hud_add({
- type = "text",
- position = {x=1.00, y=0.30},
- alignment = {x=-1, y=1},
- text = "PvP: Dueling!",
- number = 0xFFFFFF,
- size = {x=1, y=1},
- offset = {x=-16, y=yoff*1},
- })
- local hud2 = player:hud_add({
- type = "text",
- position = {x=1.00, y=0.30},
- alignment = {x=-1, y=1},
- text = "Participants: " .. #(armor.get_likely_opponents(player, duel_pos)),
- number = 0xFFFFFF,
- size = {x=1, y=1},
- offset = {x=-16, y=yoff*2},
- })
- local hud3 = player:hud_add({
- type = "text",
- position = {x=1.00, y=0.30},
- alignment = {x=-1, y=1},
- text = "Spawnpoints: " .. #(armor.get_public_spawns(duel_pos)),
- number = 0xFFFFFF,
- size = {x=1, y=1},
- offset = {x=-16, y=yoff*3},
- })
- local cb = city_block.get_block(duel_pos)
- local arena_name = ""
- if cb.area_name and cb.area_name ~= "" then
- arena_name = cb.area_name
- end
- local hud4 = player:hud_add({
- type = "text",
- position = {x=1.00, y=0.30},
- alignment = {x=-1, y=1},
- text = "Arena: " .. arena_name,
- number = 0xFFFFFF,
- size = {x=1, y=1},
- offset = {x=-16, y=yoff*0},
- })
- local hud5 = player:hud_add({
- type = "waypoint",
- name = "Arena Marker",
- number = 0xFFFFFF,
- world_pos = duel_pos,
- })
- local beds = {}
- local spawns = armor.get_public_spawns(duel_pos)
- for k = 1, #spawns do
- local id = player:hud_add({
- type = "waypoint",
- name = "Respawn Point",
- number = 0xFFFFFF,
- world_pos = spawns[k],
- precision = 0,
- })
- beds[#beds + 1] = id
- end
- -- The "Respawn" HUD counter. Shown only when dead and busy respawning.
- local hud6 = player:hud_add({
- type = "text",
- position = {x=0.50, y=0.40},
- alignment = {x=0, y=0},
- text = "",
- number = 0xFFFFFF,
- size = {x=3, y=1},
- offset = {x=0, y=0},
- })
- dueling_players[pname] = {
- start_time = os.time(),
- start_pos = duel_pos,
- out_of_bounds = 0,
- hud = {hud1, hud2, hud3, hud4, hud5, hud6, beds},
- }
- announce_begin(pname)
- chat_core.alert_player_sound(pname)
- minetest.after(1, function() armor.check_bounds(pname) end)
- return true
- end
- -- End current duel if one in progress.
- function armor.end_duel(player, reason)
- local pname = player:get_player_name()
- if dueling_players[pname] then
- local data = dueling_players[pname]
- if data.hud then
- for k = 1, #data.hud do
- if type(data.hud[k]) == "table" then
- for j = 1, #data.hud[k] do
- player:hud_remove(data.hud[k][j])
- end
- else
- player:hud_remove(data.hud[k])
- end
- end
- end
- data.hud = nil
- dueling_players[pname] = nil
- set_visible(player, true)
- announce_end(pname, reason)
- chat_core.alert_player_sound(pname)
- end
- end
- -- Get nearby players, not admins, not self.
- function armor.get_likely_opponents(player, pos)
- local targets = {}
- local pname = player:get_player_name()
- local players = minetest.get_connected_players()
- for k = 1, #players do
- local pref = players[k]
- if not gdac.player_is_admin(pref) then
- if pname ~= pref:get_player_name() then
- if vector_distance(pos, pref:get_pos()) < OPPONENT_DISTANCE then
- -- The player needs to have signaled their intent to duel.
- if dueling_players[pref:get_player_name()] then
- targets[#targets + 1] = pref
- end
- end
- end
- end
- end
- -- Sort opponents, nearest players first.
- table.sort(targets,
- function(a, b)
- local d1 = vector_distance(a:get_pos(), pos)
- local d2 = vector_distance(b:get_pos(), pos)
- return d1 < d2
- end)
- return targets
- end
- -- Get nearby public spawns IN a PvP zone.
- function armor.get_public_spawns(pos)
- local targets = {}
- local spawns = beds.nearest_public_spawns(pos, 5, PUBLIC_BED_DISTANCE)
- for k = 1, #spawns do
- -- Only include public spawns which are in a PvP arena.
- if city_block:in_pvp_arena(spawns[k]) then
- targets[#targets + 1] = spawns[k]
- end
- end
- -- Already sorted by distance.
- return targets
- end
- local function print_message(victim, punch_info)
- local pname = victim:get_player_name()
- local kname = punch_info.hitter
- local spamkey = "duel:" .. pname .. ":" .. kname
- if pname == punch_info.victim and kname == punch_info.hitter then
- if not spam.test_key(spamkey) then
- local msg
- if pname == kname then
- msg = DUEL_SUICIDE_STRINGS[math_random(1, #DUEL_SUICIDE_STRINGS)]
- elseif punch_info.stomp then
- msg = DUEL_STOMP_STRINGS[math_random(1, #DUEL_STOMP_STRINGS)]
- elseif punch_info.arrow then
- msg = DUEL_ARROW_STRINGS[math_random(1, #DUEL_ARROW_STRINGS)]
- else
- msg = DUEL_MELEE_STRINGS[math_random(1, #DUEL_MELEE_STRINGS)]
- end
- -- I can hear the snowflakes screaming "sexist" rn LOL.
- -- This is like holy water on a vampire!
- local psex = skins.get_gender_strings(pname)
- local ksex = skins.get_gender_strings(kname)
- msg = msg:gsub("<loser>", "<" .. rename.gpn(pname) .. ">")
- msg = msg:gsub("<winner>", "<" .. rename.gpn(kname) .. ">")
- msg = string.gsub(msg, "<w_himself>", ksex.himself)
- msg = string.gsub(msg, "<w_his>", ksex.his)
- msg = string.gsub(msg, "<w_him>", ksex.him)
- msg = string.gsub(msg, "<w_he>", ksex.he)
- msg = string.gsub(msg, "<l_himself>", psex.himself)
- msg = string.gsub(msg, "<l_his>", psex.his)
- msg = string.gsub(msg, "<l_him>", psex.him)
- msg = string.gsub(msg, "<l_he>", psex.he)
- -- Weapon name, or default description.
- local function weapon_string(msg, key, name)
- if string.find(msg, key) then
- local pref = minetest.get_player_by_name(name)
- if pref then
- local wield = pref:get_wielded_item()
- local def = minetest.registered_items[wield:get_name()]
- local meta = wield:get_meta()
- local description = meta:get_string("description")
- if description ~= "" then
- msg = string.gsub(msg, key, "'" .. utility.get_short_desc(description):trim() .. "'")
- elseif def and def.description then
- local str = utility.get_short_desc(def.description)
- if str == "" then
- str = "Potato Fist"
- end
- msg = string.gsub(msg, key, str)
- end
- else
- msg = string.gsub(msg, key, "Vanishing Act")
- end
- end
- return msg
- end
- msg = weapon_string(msg, "<w_weapon>", kname)
- msg = weapon_string(msg, "<l_weapon>", pname)
- minetest.chat_send_all("# Server: " .. msg)
- spam.mark_key(spamkey, 10)
- end
- end
- end
- local function heal_player(pname)
- local pref = minetest.get_player_by_name(pname)
- if not pref then
- return
- end
- local hp_max = pova.get_active_modifier(pref, "properties").hp_max
- pref:set_hp(hp_max, {reason="heal_command"})
- sprint.set_stamina(pref, SPRINT_STAMINA)
- bones.nohack.on_respawnplayer(pref)
- end
- local function lock_player_at_spawn(player, respawn_pos)
- local pname = player:get_player_name()
- -- Do this soon.
- -- This is a hacky way to prevent the player from being allowed to move.
- local function donext()
- local player = minetest.get_player_by_name(pname)
- if not player then
- return
- end
- local obj = minetest.add_entity(respawn_pos, "3d_armor:pvpduel_respawn")
- if not obj then
- return
- end
- local ent = obj:get_luaentity()
- if ent then
- ent.player_name = pname
- player:set_attach(obj)
- end
- end
- -- If we don't delay a little, other observing clients won't pick up
- -- that the player moved, and they'll just appear to be standing in the middle
- -- of the field until the respawn countdown finishes, at which point other
- -- clients will see them zip to the bed location.
- minetest.after(0.5, donext)
- end
- local function respawn_victim(player, respawn_pos)
- -- Move the player also.
- player:set_pos(respawn_pos)
- local pname = player:get_player_name()
- -- Re-engage respawn protection.
- -- This will disable if they hit anybody.
- -- It will also turn off if they leave spawn.
- local duel_info = dueling_players[pname]
- duel_info.no_respawn_protection = nil
- duel_info.respawn_pos = respawn_pos
- duel_info.respawn_countdown = RESPAWN_TIME
- -- This is to 1) hide a glitch where sometimes other client's don't notice
- -- that the player was teleported back to a respawn (this might have something
- -- to do with them not knowing about the player getting attached to the
- -- respawn entity), and 2) to prevent other players from knowing their respawn
- -- location right away.
- set_visible(player, false)
- -- Note: player is allowed to interact with themselves and nearby objects
- -- during respawn countdown. Keep this as a feature, NOT a bug! It allows them
- -- to do something while they wait a few seconds, such as switch their
- -- weapons/armor to prepare for the next round.
- preload_tp.execute({
- player_name = pname,
- target_position = respawn_pos,
- emerge_radius = 32,
- post_teleport_callback = function()
- ambiance.sound_play("respawn", respawn_pos, 0.5, 10)
- minetest.after(1, heal_player, pname)
- local pref = minetest.get_player_by_name(pname)
- if pref then
- lock_player_at_spawn(pref, respawn_pos)
- end
- end,
- force_teleport = true,
- send_blocks = false,
- particle_effects = false,
- })
- end
- -- Query whether a position is within the safe zone of any respawn point in a
- -- radius from a dueling arena position.
- function armor.in_pvp_respawn_area(pos, arena_pos)
- local spawns = armor.get_public_spawns(arena_pos)
- for k = 1, #spawns do
- if vector_distance(pos, spawns[k]) < SPAWN_SAFE_ZONE then
- return true
- end
- end
- end
- local function spawn_bones(pos, pname, hname)
- pos = vector_round(pos)
- local pref = minetest.get_player_by_name(pname)
- if not pref then
- return
- end
- local data = dueling_players[pname]
- if not data then
- return
- end
- -- Prevent placing bones near any of the public spawns.
- if armor.in_pvp_respawn_area(pos, data.start_pos) then
- return
- end
- pos = armor.find_ground_by_raycast(pos, pref)
- if minetest.get_node(pos).name == "air" then
- minetest.set_node(pos, {name="bones:bones_type2", param2=math_random(0, 3)})
- local meta = minetest.get_meta(pos)
- meta:set_string("infotext",
- "Duel: <" .. rename.gpn(pname) .. ">'s bones.\n" ..
- "Slain by <" .. rename.gpn(hname) .. ">.")
- meta:set_int("protection_cancel", 1)
- meta:mark_as_private("protection_cancel")
- end
- end
- local function spawn_bones_after(pos, pname, hname)
- minetest.after(0, spawn_bones, pos, pname, hname)
- end
- -- Cityblock punch handler uses this to check if a player should receive any
- -- damage at all. This is somewhat like jails, where brawling is not allowed.
- -- Return 'true' to disable damage from this punch.
- function armor.have_dueling_respawn_protection(player, hitter)
- local pname = player:get_player_name()
- local hname = hitter:get_player_name()
- local player_pos = vector_round(player:get_pos())
- if dueling_players[pname] and dueling_players[hname] then
- local duel_info = dueling_players[pname]
- -- If the hitter's respawn countdown is in progress, they cannot damage anyone!
- -- (Technically, during this period they shouldn't be able to interact.)
- if dueling_players[hname].respawn_countdown then
- return true
- end
- -- Hitter is punching, disable their respawn protection.
- dueling_players[hname].no_respawn_protection = true
- debug_print('respawn protection canceled for: ' .. hname)
- -- Shortcut if respawn protection is already disabled for this player.
- if duel_info.no_respawn_protection then
- debug_print('no respawn protection: ' .. pname)
- return
- end
- -- If respawn protection isn't explicitly turned off, and they're in a spawn
- -- area, then prevent damage from this punch.
- if armor.in_pvp_respawn_area(player_pos, duel_info.start_pos) then
- local key = "duel:spawnprotection:" .. pname
- if not spam.test_key(key) then
- minetest.chat_send_player(pname, "# Server: You were hit, but it reflected off your respawn protection.")
- spam.mark_key(key, 2)
- end
- return true
- end
- end
- end
- -- Called from the armor HP-change code only if player would die.
- -- Don't put logic here that needs to run for every punch, use the punch handler
- -- for that.
- function armor.handle_pvp_arena_death(hp_change, player)
- local pname = player:get_player_name()
- local player_pos = vector_round(player:get_pos())
- debug_print('pos: ' .. minetest.pos_to_string(player_pos) .. ': ' .. pname)
- -- Player must have signaled their intent to duel.
- if dueling_players[pname] then
- debug_print('dead player is dueling: ' .. pname)
- local duel_info = dueling_players[pname]
- local recently_in_arena = (duel_info.out_of_bounds > 0)
- -- PvP arena must be marked and protected.
- if recently_in_arena or city_block:in_pvp_arena(player_pos) then
- debug_print('in pvp arena: ' .. pname)
- if recently_in_arena or minetest.test_protection(player_pos, "") then
- debug_print('is_protected: ' .. pname)
- local opponents = armor.get_likely_opponents(player, duel_info.start_pos)
- local spawns = armor.get_public_spawns(duel_info.start_pos)
- debug_print('opponents: ' .. #opponents .. ': ' .. pname)
- debug_print('spawns: ' .. #spawns .. ': ' .. pname)
- -- Get notified punch info (from cityblock punch handler callback).
- -- Set global punch info to nil so we don't mistakenly use stale data later.
- local punch_info = ACTIVE_DUEL_PUNCH
- ACTIVE_DUEL_PUNCH = nil
- -- If punch info is nil at this point, the damage was caused by
- -- something other than a player. Could be env, could be mob, could even
- -- be a stray :set_hp() somewhere in the code. Use the last known punch
- -- table, if it is not too old.
- --
- -- This is also a feature: if a player manages to cause env damage to
- -- another player as a result of a normal punch, this will attribute the
- -- env damage to the hitter if the time separation is short enough.
- --
- -- This will work no matter how many times non-player-punch damage is
- -- applied, as long as the damage occurs shortly after the initial punch.
- if not punch_info then
- if os.time() <= (duel_info.last_punch_time + ENV_TIME_AFTER_PUNCH) then
- punch_info = duel_info.last_punch_table
- end
- end
- debug_print('punch info: ' .. dump(punch_info) .. ': ' .. pname)
- -- There must be nearby opponents and nearby spawns.
- -- No opponents == no duel, no spawns == not valid arena.
- if #opponents > 0 and #spawns > 0 and punch_info then
- -- The hitter must also be in the duel.
- -- PvP arenas are not meant to be used as defense against bastards!
- -- Duels are between friends, or frenemies.
- local hitter_is_dueling = false
- if punch_info.hitter and dueling_players[punch_info.hitter] then
- hitter_is_dueling = true
- end
- if hitter_is_dueling then
- -- If player has only 1 HP, they were already "dead" as far as we're concerned.
- -- However, it may transpire that a player gets to 1 hp naturally.
- -- So the only way to know if we should respawn the player is this:
- -- do they have a respawn countdown currently in progress? If yes,
- -- then they were already "killed" and we should skip this.
- if not duel_info.respawn_countdown then
- debug_print('handling duel death: ' .. pname)
- -- Get them off of whatever.
- default.detach_player_if_attached(player)
- -- Death sound needs to play before we respawn the player.
- coresounds.play_death_sound(player, pname)
- -- We MUST wait until next server step to spawn bones, because
- -- bones cancel protection, which would confuse the arena code and
- -- cause the player to die a real death! This can happen if two or
- -- more players die at the exact same time in the same spot
- -- (e.g., murder-suicide with a TNT arrow).
- spawn_bones_after(player_pos, pname, punch_info.hitter)
- -- Send taunt.
- print_message(player, punch_info)
- -- Send victim to a respawn point.
- respawn_victim(player, spawns[math_random(1, #spawns)])
- end
- debug_print('preventing real death: ' .. pname)
- -- Prevent real death, and all its consequences.
- -- Player will be fully healed after they teleport to a public spawn.
- -- Note: if player HP is 1, this should return 0 (no hp change allowed).
- if player:get_hp() <= 1 then
- return 0
- end
- return -(player:get_hp() - 1)
- end
- end
- else
- debug_print('NOT PROTECTED: ' .. pname)
- end
- end
- -- Player is dueling, but arena checks didn't pass.
- end
- -- Otherwise, do not interfere with normal damage to player.
- return hp_change
- end
- -- Used to query if this location is a valid combat arena.
- function armor.is_valid_arena(pos)
- pos = vector_round(pos)
- if city_block:in_pvp_arena(pos) then
- if minetest.test_protection(pos, "") then
- if #(armor.get_public_spawns(pos)) >= 2 then
- return true
- end
- end
- end
- end
- -- Called by the cityblock punch handler.
- function armor.notify_duel_punch(victim_name, hitter_name, stomp_flag, ranged_flag)
- ACTIVE_DUEL_PUNCH = {
- victim = victim_name,
- hitter = hitter_name,
- stomp = stomp_flag,
- arrow = ranged_flag,
- }
- -- Also set this table on the victim's dueling info.
- local data = dueling_players[victim_name]
- if data then
- data.last_punch_table = ACTIVE_DUEL_PUNCH
- data.last_punch_time = os.time()
- end
- end
- function armor.clear_duel_punch()
- ACTIVE_DUEL_PUNCH = nil
- end
- if not armor.duel_registered then
- armor.duel_registered = true
- local entity_def = {
- visual = "wielditem",
- visual_size = {x=0, y=0},
- collisionbox = {0, 0, 0, 0, 0, 0},
- physical = false,
- textures = {"air"},
- is_visible = false,
- static_save = false,
- --[[
- on_activate = function(self, staticdata, dtime_s)
- if self._play_immediate then
- self._ptime = 0
- self._ctime = 0
- end
- end,
- on_punch = function(self, puncher, time_from_last_punch, tool_caps, dir)
- end,
- on_death = function(self, killer)
- end,
- on_rightclick = function(self, clicker)
- end,
- get_staticdata = function(self)
- return ""
- end,
- --]]
- on_punch = function(self, puncher, time_from_last_punch, tool_caps, dir)
- end,
- on_blast = function()
- return false, false, {}
- end,
- detach_player = function(self)
- if self.player_name then
- local pref = minetest.get_player_by_name(self.player_name)
- if pref then
- self.player_name = nil
- pref:set_detach()
- self.object:remove()
- end
- end
- end,
- on_step = function(self, dtime)
- if self.player_name then
- local data = dueling_players[self.player_name]
- if not data then
- self.object:remove()
- return
- end
- -- Will be nil when the countdown has ended.
- if not data.respawn_countdown then
- local pref = minetest.get_player_by_name(self.player_name)
- if pref then
- self.player_name = nil
- pref:set_detach()
- self.object:remove()
- end
- else
- -- Keep attaching.
- local pref = minetest.get_player_by_name(self.player_name)
- if pref then
- pref:set_attach(self.object)
- end
- end
- end
- end,
- }
- minetest.register_entity("3d_armor:pvpduel_respawn", entity_def)
- end
|