forked from Simnation/Main
ed
This commit is contained in:
parent
bc2a683f6f
commit
efaef09a83
2 changed files with 152 additions and 333 deletions
|
@ -22,6 +22,16 @@ local function debugPrint(message)
|
|||
print("^2[TDM DEBUG]^7 " .. message)
|
||||
end
|
||||
|
||||
-- Funktion zum Prüfen, ob eine Waffe eine Airsoft-Waffe ist
|
||||
function isAirsoftWeapon(weaponHash)
|
||||
return Config.airsoftWeapons[weaponHash] or Config.treatAllWeaponsAsAirsoft
|
||||
end
|
||||
|
||||
-- Funktion zum Prüfen, ob der Spieler im Ragdoll-Zustand ist
|
||||
function isPedInRagdoll(ped)
|
||||
return IsPedRagdoll(ped) or IsPedFalling(ped) or IsPedDiving(ped)
|
||||
end
|
||||
|
||||
-- Events
|
||||
RegisterNetEvent('tdm:updateGamesList', function(games)
|
||||
activeGames = games
|
||||
|
@ -169,12 +179,15 @@ RegisterNetEvent('tdm:playerHit', function()
|
|||
-- Benachrichtigung
|
||||
lib.notify({
|
||||
title = 'TeamDeathmatch',
|
||||
description = 'Du wurdest getroffen! Respawn in 3 Sekunden...',
|
||||
description = 'Du wurdest getroffen! Respawn in ' .. (Config.respawnDelay / 1000) .. ' Sekunden...',
|
||||
type = 'error'
|
||||
})
|
||||
|
||||
-- Verbesserte Respawn-Logik
|
||||
SetTimeout(3000, function()
|
||||
-- Optional: Ragdoll aktivieren
|
||||
SetPedToRagdoll(ped, 2000, 2000, 0, true, true, false)
|
||||
|
||||
-- Verbesserte Respawn-Logik mit automatischem Teleport
|
||||
SetTimeout(Config.respawnDelay, function()
|
||||
if not inTDM then
|
||||
debugPrint("Respawn abgebrochen - nicht mehr im TDM")
|
||||
return
|
||||
|
@ -209,7 +222,7 @@ RegisterNetEvent('tdm:playerHit', function()
|
|||
-- Alle Animationen stoppen
|
||||
ClearPedTasksImmediately(ped)
|
||||
|
||||
-- Teleport
|
||||
-- Teleport direkt zum Spawn-Punkt
|
||||
SetEntityCoords(ped, randomSpawn.x, randomSpawn.y, randomSpawn.z)
|
||||
SetEntityHealth(ped, GetEntityMaxHealth(ped))
|
||||
|
||||
|
@ -621,7 +634,7 @@ CreateThread(function()
|
|||
end
|
||||
end)
|
||||
|
||||
-- Verbesserte Damage Handler für korrekte Treffer-Registrierung
|
||||
-- Verbesserte Damage Handler für Airsoft-Waffen
|
||||
CreateThread(function()
|
||||
while true do
|
||||
if inTDM and not isHit then
|
||||
|
@ -629,22 +642,30 @@ CreateThread(function()
|
|||
|
||||
-- Prüfe, ob der Spieler Schaden genommen hat
|
||||
if HasEntityBeenDamagedByAnyPed(ped) then
|
||||
debugPrint("Schaden erkannt - Identifiziere Angreifer")
|
||||
debugPrint("Schaden erkannt - Identifiziere Angreifer und Waffe")
|
||||
|
||||
local damager = nil
|
||||
local damagerPlayer = nil
|
||||
local weaponHash = 0
|
||||
|
||||
-- Versuche den Angreifer zu identifizieren
|
||||
-- Versuche den Angreifer und die Waffe zu identifizieren
|
||||
for _, player in ipairs(GetActivePlayers()) do
|
||||
local playerPed = GetPlayerPed(player)
|
||||
if HasPedBeenDamagedBy(ped, playerPed) then
|
||||
damager = playerPed
|
||||
damagerPlayer = GetPlayerServerId(player)
|
||||
debugPrint("Angreifer identifiziert: " .. damagerPlayer)
|
||||
|
||||
-- Versuche die Waffe zu ermitteln
|
||||
weaponHash = GetSelectedPedWeapon(playerPed)
|
||||
debugPrint("Angreifer identifiziert: " .. damagerPlayer .. " mit Waffe: " .. weaponHash)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- Prüfe, ob es eine Airsoft-Waffe ist oder alle Waffen als Airsoft behandelt werden
|
||||
if isAirsoftWeapon(weaponHash) then
|
||||
debugPrint("Airsoft-Treffer erkannt mit Waffe: " .. weaponHash)
|
||||
|
||||
-- Schaden zurücksetzen
|
||||
ClearEntityLastDamageEntity(ped)
|
||||
ClearPedLastWeaponDamage(ped)
|
||||
|
@ -661,6 +682,12 @@ CreateThread(function()
|
|||
|
||||
-- Warten um mehrfache Auslösung zu verhindern
|
||||
Wait(500)
|
||||
else
|
||||
debugPrint("Keine Airsoft-Waffe erkannt: " .. weaponHash)
|
||||
-- Schaden trotzdem zurücksetzen
|
||||
ClearEntityLastDamageEntity(ped)
|
||||
SetEntityHealth(ped, GetEntityMaxHealth(ped))
|
||||
end
|
||||
end
|
||||
Wait(0)
|
||||
else
|
||||
|
@ -676,6 +703,7 @@ AddEventHandler('gameEventTriggered', function(name, args)
|
|||
local attackerId = args[2]
|
||||
local isDead = args[4] == 1
|
||||
local weaponHash = args[5]
|
||||
local isMelee = args[10] == 1
|
||||
|
||||
if inTDM and not isHit and victimId == PlayerPedId() then
|
||||
local attackerServerId = nil
|
||||
|
@ -690,7 +718,8 @@ AddEventHandler('gameEventTriggered', function(name, args)
|
|||
|
||||
debugPrint("Schaden-Event erkannt: Angreifer=" .. (attackerServerId or "NPC/Unbekannt") .. ", Waffe=" .. weaponHash)
|
||||
|
||||
if attackerServerId then
|
||||
-- Prüfe, ob es eine Airsoft-Waffe ist oder alle Waffen als Airsoft behandelt werden
|
||||
if isAirsoftWeapon(weaponHash) and attackerServerId then
|
||||
-- Lokale Stats sofort updaten
|
||||
playerStats.deaths = playerStats.deaths + 1
|
||||
|
||||
|
@ -705,328 +734,91 @@ AddEventHandler('gameEventTriggered', function(name, args)
|
|||
end
|
||||
end)
|
||||
|
||||
-- Death Handler
|
||||
-- Direkter Waffen-Schaden Monitor für zusätzliche Zuverlässigkeit
|
||||
CreateThread(function()
|
||||
while true do
|
||||
Wait(1000)
|
||||
|
||||
if inTDM then
|
||||
Wait(0)
|
||||
if inTDM and not isHit then
|
||||
local ped = PlayerPedId()
|
||||
|
||||
if IsEntityDead(ped) then
|
||||
debugPrint("Spieler ist tot!")
|
||||
-- Prüfe auf Projektil-Treffer
|
||||
if HasEntityBeenDamagedByWeapon(ped, 0, 2) then -- 2 = Projektilwaffen
|
||||
debugPrint("Projektil-Treffer erkannt")
|
||||
|
||||
TriggerServerEvent('tdm:playerDied', currentGameId)
|
||||
|
||||
lib.notify({
|
||||
title = 'TeamDeathmatch',
|
||||
description = 'Du bist ausgeschieden!',
|
||||
type = 'error'
|
||||
})
|
||||
|
||||
Wait(3000)
|
||||
TriggerServerEvent('tdm:leaveGame')
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- NPC Setup für alle Felder
|
||||
CreateThread(function()
|
||||
-- Für jedes Spielfeld Blip und NPC erstellen
|
||||
for fieldId, fieldData in pairs(Config.gameFields) do
|
||||
if fieldData.lobby and fieldData.lobby.pos and fieldData.lobby.npc then
|
||||
local lobbyPos = fieldData.lobby.pos
|
||||
local npcData = fieldData.lobby.npc
|
||||
|
||||
-- Blip erstellen
|
||||
local blip = AddBlipForCoord(lobbyPos.x, lobbyPos.y, lobbyPos.z)
|
||||
SetBlipSprite(blip, 432)
|
||||
SetBlipDisplay(blip, 4)
|
||||
SetBlipScale(blip, 0.8)
|
||||
SetBlipColour(blip, 1)
|
||||
SetBlipAsShortRange(blip, true)
|
||||
BeginTextCommandSetBlipName("STRING")
|
||||
AddTextComponentString("TDM - " .. fieldData.name)
|
||||
EndTextCommandSetBlipName(blip)
|
||||
|
||||
tdmBlips[fieldId] = blip
|
||||
|
||||
-- NPC erstellen
|
||||
RequestModel(GetHashKey(npcData.model))
|
||||
while not HasModelLoaded(GetHashKey(npcData.model)) do
|
||||
Wait(1)
|
||||
end
|
||||
|
||||
local npc = CreatePed(4, GetHashKey(npcData.model), npcData.coords.x, npcData.coords.y, npcData.coords.z, npcData.coords.w, false, true)
|
||||
SetEntityInvincible(npc, true)
|
||||
FreezeEntityPosition(npc, true)
|
||||
SetBlockingOfNonTemporaryEvents(npc, true)
|
||||
|
||||
spawnedNPCs[fieldId] = npc
|
||||
|
||||
-- Target für diesen NPC
|
||||
exports['qb-target']:AddTargetEntity(npc, {
|
||||
options = {
|
||||
{
|
||||
type = "client",
|
||||
event = "tdm:openFieldMenu",
|
||||
icon = "fas fa-crosshairs",
|
||||
label = "TeamDeathmatch - " .. fieldData.name,
|
||||
fieldId = fieldId
|
||||
},
|
||||
},
|
||||
distance = 2.5
|
||||
})
|
||||
|
||||
debugPrint("NPC und Blip für Feld " .. fieldId .. " erstellt")
|
||||
else
|
||||
debugPrint("WARNUNG: Feld " .. fieldId .. " hat keine vollständige Lobby-Konfiguration!")
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Event für Feld-spezifisches Menü
|
||||
RegisterNetEvent('tdm:openFieldMenu', function(data)
|
||||
if data and data.fieldId then
|
||||
openMainMenu(data.fieldId)
|
||||
else
|
||||
lib.notify({
|
||||
title = 'Fehler',
|
||||
description = 'Keine Feld-ID übertragen!',
|
||||
type = 'error'
|
||||
})
|
||||
end
|
||||
end)
|
||||
|
||||
-- Chat Command zum Spiel verlassen
|
||||
RegisterCommand('leavetdm', function()
|
||||
if inTDM then
|
||||
TriggerServerEvent('tdm:leaveGame')
|
||||
lib.notify({
|
||||
title = 'TeamDeathmatch',
|
||||
description = 'Du hast das Spiel über Command verlassen!',
|
||||
type = 'info'
|
||||
})
|
||||
else
|
||||
lib.notify({
|
||||
title = 'TeamDeathmatch',
|
||||
description = 'Du bist in keinem Spiel!',
|
||||
type = 'error'
|
||||
})
|
||||
end
|
||||
end, false)
|
||||
|
||||
-- Keybind zum Spiel verlassen
|
||||
RegisterKeyMapping('leavetdm', 'TeamDeathmatch verlassen', 'keyboard', 'F7')
|
||||
|
||||
-- Debug Command zum Testen der Config
|
||||
RegisterCommand('debugtdm', function()
|
||||
debugPrint("Aktuelle Werte:")
|
||||
debugPrint("inTDM: " .. tostring(inTDM))
|
||||
debugPrint("currentField: " .. tostring(currentField))
|
||||
debugPrint("currentLobbyField: " .. tostring(currentLobbyField))
|
||||
debugPrint("currentTeam: " .. tostring(currentTeam))
|
||||
debugPrint("currentGameId: " .. tostring(currentGameId))
|
||||
debugPrint("isHit: " .. tostring(isHit))
|
||||
debugPrint("Hits: " .. playerStats.hits)
|
||||
debugPrint("Deaths: " .. playerStats.deaths)
|
||||
|
||||
debugPrint("Verfügbare Felder:")
|
||||
for fieldId, fieldData in pairs(Config.gameFields) do
|
||||
local hasLobby = fieldData.lobby and fieldData.lobby.pos and "✅" or "❌"
|
||||
debugPrint("- " .. fieldId .. ": " .. fieldData.name .. " " .. hasLobby)
|
||||
end
|
||||
end, false)
|
||||
|
||||
-- Debug Commands für Masken
|
||||
RegisterCommand('testmask', function(source, args)
|
||||
if not args[1] or not args[2] then
|
||||
lib.notify({
|
||||
title = 'Debug',
|
||||
description = 'Verwendung: /testmask [team1/team2] [male/female]',
|
||||
type = 'error'
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
local team = args[1]
|
||||
local gender = args[2]
|
||||
|
||||
if Config.teamMasks[team] and Config.teamMasks[team][gender] then
|
||||
local maskData = Config.teamMasks[team][gender]
|
||||
local ped = PlayerPedId()
|
||||
|
||||
SetPedComponentVariation(ped, maskData.component, maskData.drawable, maskData.texture, 0)
|
||||
|
||||
lib.notify({
|
||||
title = 'Debug',
|
||||
description = 'Maske gesetzt: ' .. team .. ' (' .. gender .. ')',
|
||||
type = 'success'
|
||||
})
|
||||
else
|
||||
lib.notify({
|
||||
title = 'Debug',
|
||||
description = 'Maske nicht gefunden!',
|
||||
type = 'error'
|
||||
})
|
||||
end
|
||||
end, false)
|
||||
|
||||
-- Command zum Entfernen der Maske
|
||||
RegisterCommand('removemask', function()
|
||||
SetPedComponentVariation(PlayerPedId(), 1, 0, 0, 0)
|
||||
lib.notify({
|
||||
title = 'Debug',
|
||||
description = 'Maske entfernt!',
|
||||
type = 'info'
|
||||
})
|
||||
end, false)
|
||||
|
||||
-- Debug-Funktion für Respawn
|
||||
RegisterCommand('forcetdmrespawn', function()
|
||||
if inTDM and currentTeam and currentField then
|
||||
local ped = PlayerPedId()
|
||||
local fieldConfig = Config.gameFields[currentField]
|
||||
local spawnPoints = fieldConfig.teamSpawns[currentTeam]
|
||||
local randomSpawn = spawnPoints[math.random(#spawnPoints)]
|
||||
|
||||
DoScreenFadeOut(500)
|
||||
Wait(600)
|
||||
|
||||
ClearPedTasksImmediately(ped)
|
||||
NetworkResurrectLocalPlayer(randomSpawn.x, randomSpawn.y, randomSpawn.z, 0.0, true, false)
|
||||
SetEntityCoords(ped, randomSpawn.x, randomSpawn.y, randomSpawn.z)
|
||||
-- Schaden zurücksetzen
|
||||
ClearEntityLastDamageEntity(ped)
|
||||
ClearPedLastWeaponDamage(ped)
|
||||
SetEntityHealth(ped, GetEntityMaxHealth(ped))
|
||||
isHit = false
|
||||
|
||||
-- Lokale Stats sofort updaten
|
||||
playerStats.deaths = playerStats.deaths + 1
|
||||
|
||||
-- Treffer-Events auslösen
|
||||
TriggerEvent('tdm:playerHit')
|
||||
TriggerServerEvent('tdm:playerWasHit', currentGameId, currentTeam)
|
||||
|
||||
-- Warten um mehrfache Auslösung zu verhindern
|
||||
Wait(500)
|
||||
end
|
||||
else
|
||||
Wait(500)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Ragdoll-Erkennung Thread
|
||||
CreateThread(function()
|
||||
local lastDamager = nil
|
||||
local lastDamageTime = 0
|
||||
|
||||
while true do
|
||||
Wait(100)
|
||||
DoScreenFadeIn(500)
|
||||
if inTDM and not isHit then
|
||||
local ped = PlayerPedId()
|
||||
|
||||
lib.notify({
|
||||
title = 'Debug',
|
||||
description = 'Manueller Respawn durchgeführt!',
|
||||
type = 'success'
|
||||
})
|
||||
-- Speichere den letzten Angreifer, wenn Schaden genommen wurde
|
||||
if HasEntityBeenDamagedByAnyPed(ped) then
|
||||
for _, player in ipairs(GetActivePlayers()) do
|
||||
local playerPed = GetPlayerPed(player)
|
||||
if HasPedBeenDamagedBy(ped, playerPed) then
|
||||
lastDamager = GetPlayerServerId(player)
|
||||
lastDamageTime = GetGameTimer()
|
||||
debugPrint("Letzter Angreifer gespeichert: " .. lastDamager)
|
||||
break
|
||||
end
|
||||
end
|
||||
ClearEntityLastDamageEntity(ped)
|
||||
end
|
||||
|
||||
-- Prüfe, ob der Spieler im Ragdoll-Zustand ist
|
||||
if isPedInRagdoll(ped) then
|
||||
debugPrint("Ragdoll-Zustand erkannt - Zählt als Tod")
|
||||
|
||||
-- Lokale Stats sofort updaten
|
||||
playerStats.deaths = playerStats.deaths + 1
|
||||
|
||||
-- Bestimme den Angreifer (verwende den letzten Angreifer, wenn innerhalb von 3 Sekunden)
|
||||
local attacker = nil
|
||||
if lastDamager and (GetGameTimer() - lastDamageTime) < 3000 then
|
||||
attacker = lastDamager
|
||||
debugPrint("Ragdoll-Tod zugeordnet an Angreifer: " .. attacker)
|
||||
else
|
||||
lib.notify({
|
||||
title = 'Debug',
|
||||
description = 'Du bist nicht in einem TDM-Spiel!',
|
||||
type = 'error'
|
||||
})
|
||||
end
|
||||
end, false)
|
||||
|
||||
-- Debug-Funktion für Stats
|
||||
RegisterCommand('showstats', function()
|
||||
if inTDM then
|
||||
debugPrint("Lokale Stats:")
|
||||
debugPrint("Hits: " .. playerStats.hits)
|
||||
debugPrint("Deaths: " .. playerStats.deaths)
|
||||
|
||||
if currentGameId then
|
||||
TriggerServerEvent('tdm:debugPlayerStats', currentGameId)
|
||||
debugPrint("Kein Angreifer für Ragdoll-Tod gefunden")
|
||||
end
|
||||
|
||||
lib.notify({
|
||||
title = 'Debug',
|
||||
description = 'Lokale Stats - Hits: ' .. playerStats.hits .. ', Deaths: ' .. playerStats.deaths,
|
||||
type = 'info'
|
||||
})
|
||||
else
|
||||
lib.notify({
|
||||
title = 'Debug',
|
||||
description = 'Du bist nicht in einem TDM-Spiel!',
|
||||
type = 'error'
|
||||
})
|
||||
end
|
||||
end, false)
|
||||
|
||||
-- Debug-Funktion für manuellen Hit
|
||||
RegisterCommand('testhit', function()
|
||||
if inTDM then
|
||||
-- Treffer-Events auslösen
|
||||
TriggerEvent('tdm:playerHit')
|
||||
lib.notify({
|
||||
title = 'Debug',
|
||||
description = 'Test-Hit ausgelöst!',
|
||||
type = 'info'
|
||||
})
|
||||
TriggerServerEvent('tdm:playerWasHit', currentGameId, currentTeam, attacker)
|
||||
|
||||
-- Zurücksetzen des letzten Angreifers
|
||||
lastDamager = nil
|
||||
|
||||
-- Warten um mehrfache Auslösung zu verhindern
|
||||
Wait(500)
|
||||
end
|
||||
else
|
||||
lib.notify({
|
||||
title = 'Debug',
|
||||
description = 'Du bist nicht in einem TDM-Spiel!',
|
||||
type = 'error'
|
||||
})
|
||||
Wait(500)
|
||||
end
|
||||
end, false)
|
||||
|
||||
-- Debug-Funktion für manuellen Treffer
|
||||
RegisterCommand('testtreff', function()
|
||||
if inTDM then
|
||||
TriggerEvent('tdm:hitRegistered')
|
||||
lib.notify({
|
||||
title = 'Debug',
|
||||
description = 'Test-Treffer registriert!',
|
||||
type = 'info'
|
||||
})
|
||||
else
|
||||
lib.notify({
|
||||
title = 'Debug',
|
||||
description = 'Du bist nicht in einem TDM-Spiel!',
|
||||
type = 'error'
|
||||
})
|
||||
end
|
||||
end, false)
|
||||
|
||||
-- Debug-Befehl zum Testen der Treffer-Registrierung
|
||||
RegisterCommand('testtreffer', function()
|
||||
if inTDM and currentGameId and currentTeam then
|
||||
debugPrint("Teste Treffer-Registrierung")
|
||||
|
||||
-- Simuliere einen Treffer gegen sich selbst
|
||||
local targetTeam = currentTeam == 'team1' and 'team2' or 'team1'
|
||||
TriggerServerEvent('tdm:playerWasHit', currentGameId, currentTeam, GetPlayerServerId(PlayerId()))
|
||||
|
||||
lib.notify({
|
||||
title = 'Debug',
|
||||
description = 'Test-Treffer gesendet!',
|
||||
type = 'info'
|
||||
})
|
||||
else
|
||||
lib.notify({
|
||||
title = 'Debug',
|
||||
description = 'Du musst in einem TDM-Spiel sein!',
|
||||
type = 'error'
|
||||
})
|
||||
end
|
||||
end, false)
|
||||
|
||||
-- Debug-Befehl zum Testen des Respawns
|
||||
RegisterCommand('testrespawn', function()
|
||||
if inTDM then
|
||||
debugPrint("Teste Respawn-Funktion")
|
||||
TriggerEvent('tdm:playerHit')
|
||||
|
||||
lib.notify({
|
||||
title = 'Debug',
|
||||
description = 'Test-Respawn ausgelöst!',
|
||||
type = 'info'
|
||||
})
|
||||
else
|
||||
lib.notify({
|
||||
title = 'Debug',
|
||||
description = 'Du musst in einem TDM-Spiel sein!',
|
||||
type = 'error'
|
||||
})
|
||||
end
|
||||
end, false)
|
||||
|
||||
-- Hilfsfunktion für table.count
|
||||
table.count = function(tbl)
|
||||
local count = 0
|
||||
for _, _ in pairs(tbl) do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
end)
|
||||
|
||||
|
|
|
@ -156,6 +156,33 @@ Config.teamMasks = {
|
|||
-- Game Settings
|
||||
Config.maxGameTime = 600 -- 10 Minuten
|
||||
Config.maxHits = 30 -- Spiel endet bei 30 Treffern
|
||||
Config.respawnDelay = 3000 -- 3 Sekunden Respawn-Verzögerung
|
||||
|
||||
-- Airsoft-Einstellungen
|
||||
Config.treatAllWeaponsAsAirsoft = true -- Alle Waffen als Airsoft behandeln
|
||||
Config.airsoftDamageMultiplier = 0.1 -- Reduzierter Schaden für Airsoft-Waffen
|
||||
|
||||
-- Airsoft-Waffen (Hash-Werte)
|
||||
Config.airsoftWeapons = {
|
||||
-- Pistolen
|
||||
[`WEAPON_AIRSOFTGLOCK20`] = true,
|
||||
|
||||
-- SMGs
|
||||
[`WEAPON_AIRSFOTMIRCOUZI`] = true,
|
||||
[`WEAPON_AIRSOFTMP5`] = true,
|
||||
|
||||
-- Gewehre
|
||||
[`WEAPON_AIRSOFTM4`] = true,
|
||||
[`WEAPON_AIRSOFTAK47`] = true,
|
||||
[`WEAPON_AIRSOFTM249`] = true,
|
||||
[`WEAPON_AIRSOFTG46C`] = true,
|
||||
[`WEAPON_AIRSOFTR870`] = true,
|
||||
|
||||
-- Scharfschützengewehre
|
||||
[`WEAPON_AIRSOFTR700`] = true,
|
||||
|
||||
|
||||
}
|
||||
|
||||
-- Debug Settings
|
||||
Config.debugMode = true -- Set to false in production
|
||||
Config.debugMode = true -- Auf false setzen für Produktion
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue