NO FALL DAMAGE - G1R:MP (server API 0.1.2 / Protocol 31+)
Works in two layers:
1. The engine stat "resistance_falling" is set to STAT_VALUE when a player
spawns, after every revive and refreshed every REFRESH_MS (the engine may
overwrite the stat when it loads the character).
2. Fallback: if damage of a DAMAGE_TYPES kind with no attacker still arrives
(that is how the engine reports a fall), health is restored to the value
it had before the hit.
<?xml version="1.0" encoding="utf-8"?>
<scripts>
<script src="server.lua" type="server" />
</scripts>
Server Script
Works in two layers:
1. The engine stat "resistance_falling" is set to STAT_VALUE when a player
spawns, after every revive and refreshed every REFRESH_MS (the engine may
overwrite the stat when it loads the character).
2. Fallback: if damage of a DAMAGE_TYPES kind with no attacker still arrives
(that is how the engine reports a fall), health is restored to the value
it had before the hit.
<?xml version="1.0" encoding="utf-8"?>
<scripts>
<script src="server.lua" type="server" />
</scripts>
Server Script
Quote:-- === CONFIG ==============================================================
local ENABLED_BY_DEFAULT = true -- every player from the moment they join
local STAT_VALUE = 100000 -- resistance_falling; 0 = normal fall damage
local REFRESH_MS = 15000 -- stat refresh interval (ms)
local DAMAGE_TYPES = {environment = true, fall = true, falling = true} -- damage kinds reported as a fall
local RESTORE_HEALTH = true -- fallback: undo fall damage by restoring health
local LOG = false -- outputDebugString on every restore
local ADMIN_PERMISSION = "admin.world" -- /nofall on|off: permission checked through the gothic_rp bridge
-- =========================================================================
local enabled = {} -- playerId -> true/false (nil = default)
local previous = {} -- playerId -> stat value before enabling (restored on disable/stop)
local function isEnabled(playerId)
if enabled[playerId] == nil then return ENABLED_BY_DEFAULT end
return enabled[playerId] == true
end
local function applyStat(playerId)
if not isPlayerConnected(playerId) then return false end
if isEnabled(playerId) then
if previous[playerId] == nil then
previous[playerId] = tonumber(getPlayerStat(playerId, "resistance_falling")) or 0
end
return setPlayerStat(playerId, "resistance_falling", STAT_VALUE) ~= false
elseif previous[playerId] ~= nil then
setPlayerStat(playerId, "resistance_falling", previous[playerId])
previous[playerId] = nil
end
return true
end
-- 1) the stat: on spawn, on revive, periodic refresh
addEventHandler("onPlayerSpawn", root, function(playerId)
setTimer(function() applyStat(playerId) end, 500, 1) -- the engine may still be loading stats right after spawn
end)
addEventHandler("onPlayerRevived", root, function(playerId)
setTimer(function() applyStat(playerId) end, 500, 1)
end)
setTimer(function()
for _, playerId in ipairs(getPlayers()) do applyStat(playerId) end
end, REFRESH_MS, 0)
-- 2) fallback: restore health after fall damage (no attacker)
addEventHandler("onPlayerDamage", root, function(playerId, damage, healthAfter, attackerId, damageType)
if not RESTORE_HEALTH or not isEnabled(playerId) then return end
local kind = tostring(damageType or ""):lower()
if not DAMAGE_TYPES[kind] then return end
local attacker = tonumber(attackerId)
if attacker and attacker >= 0 then return end -- hit by someone or something - not a fall
damage = tonumber(damage) or 0
healthAfter = tonumber(healthAfter) or 0
if damage <= 0 or healthAfter <= 0 then return end -- death cannot be undone here
local restored = math.min(healthAfter + damage, tonumber(getPlayerMaxHealth(playerId)) or (healthAfter + damage))
setPlayerHealth(playerId, restored)
if LOG then
outputDebugString(("[nofall] player=%s restored %d fall damage (%s)"):format(tostring(playerId), damage, kind), 3)
end
end)
-- API for other resources
addEvent("nofall:set", false)
addEventHandler("nofall:set", root, function(playerId, state)
playerId = tonumber(playerId)
if not playerId then return end
enabled[playerId] = state ~= false
applyStat(playerId)
end)
addEventHandler("onPlayerQuit", root, function(playerId)
enabled[playerId] = nil
previous[playerId] = nil
end)
-- Admin command: permission via gothic_rp's synchronous event bridge (permission_bridge.lua);
-- refused when gothic_rp is not running.
local bridgeGranted = false
addEvent("g1r:permissionSeen", false)
addEvent("g1r:permissionGrant", false)
addEventHandler("g1r:permissionGrant", root, function() bridgeGranted = true end)
local function isWorldAdmin(playerId)
bridgeGranted = false
triggerEvent("g1r:permissionQuery", root, playerId, ADMIN_PERMISSION)
return bridgeGranted
end
addCommandHandler("nofall", function(playerId, commandName, mode)
if not isWorldAdmin(playerId) then
outputChatBox("No permission.", playerId, 230, 90, 90, false)
return
end
mode = tostring(mode or ""):lower()
if mode == "on" then enabled[playerId] = true elseif mode == "off" then enabled[playerId] = false end
applyStat(playerId)
outputChatBox(("No fall damage: %s (/nofall on|off)."):format(isEnabled(playerId) and "ENABLED" or "disabled"), playerId, 216, 185, 120, false)
end)
addEventHandler("onResourceStart", resourceRoot, function()
for _, playerId in ipairs(getPlayers()) do applyStat(playerId) end
outputDebugString(("[nofall] start: default %s, resistance_falling=%d, restore health=%s"):format(
ENABLED_BY_DEFAULT and "enabled" or "disabled", STAT_VALUE, tostring(RESTORE_HEALTH)), 3)
end)
addEventHandler("onResourceStop", resourceRoot, function()
for _, playerId in ipairs(getPlayers()) do
if previous[playerId] ~= nil then setPlayerStat(playerId, "resistance_falling", previous[playerId]) end
end
end)