Live
Forensik und Tatorte
Ein Ermittlungssystem für einen Polizei-Roleplay-Server: Leichen, die bestehen bleiben, Beweise, die standhalten, und Fallakten, die Ermittler durchsuchen können. Ich habe es halbfertig und kaputt übernommen und zu einem funktionierenden System ausgebaut.
- Rolle
- Ein unfertiges Plugin übernommen, dann neu aufgebaut, erweitert und gewartet
- Stack
- Lua (GLua), NutScript, MySQL
- Kontext
- Polizei- und Kriminellen-Roleplay bei Diverge Networks
- Status
- Live, seit Dezember 2024 im Einsatz
Screenshots und Clips auf dieser Seite: aufgenommen auf einem Entwicklungsserver mit Testcharakteren und gestellten Daten.
Das Problem
Als ich zu Diverge Networks kam, hatte der Server bereits ein Forensik-Plugin, das ein früherer Entwickler geschrieben und nie fertiggestellt hatte. Leichen wurden dupliziert oder verschwanden, Beweise überlebten keinen Map-Wechsel, und manche Fingerabdrücke ließen sich gar nicht sichern. Polizei- und Kriminellen-Roleplay stützten sich trotzdem darauf.
Über fast zwei Jahre und rund 70 Commits habe ich es zu dem System ausgebaut, das das Roleplay brauchte: Tatorte, die bestehen bleiben, Beweise, die standhalten, und Fallakten, mit denen Ermittler tatsächlich arbeiten können. Die Namensnennung des ursprünglichen Autors habe ich im Code beibehalten.
Leichen und Tatorte
- Leichen bleiben dort bestehen, wo die Person gestorben ist, über Neustarts und Map-Wechsel hinweg, mit festgehaltener Todeszeit.
- Die Untersuchung einer Leiche zeigt, woran die Person gestorben ist, ihre jüngsten Verletzungen und die Waffen, die sie zu diesem Zeitpunkt bei sich trug. Hunger und Entzug erscheinen nicht mehr als gewaltsame Verletzungen.
- Kriminelle können eine Leiche beseitigen: sie in einzelne Körperteil-Items zerlegen, mit Benzin verbrennen, sodass der Körper sichtbar verkohlt, oder in einen Leichensack packen. Aus einem Sack geholte Leichen behalten alles, was sie hatten.
- Wer eine Leiche zieht, wird langsamer, und die Verlangsamung endet immer, auch wenn die Leiche mitten im Ziehen gelöscht wird.
- Die Fraktion des Täters wird an der Leiche vermerkt. Nur die Polizei und diese Fraktion können sie bewegen, und nur diese Fraktion kann die Hülsen zusammenfegen, sodass Unbeteiligte die Beweise nicht außerhalb der Rolle zerstören können.
- Blut lässt sich in einem kurzen Minispiel aufwischen, und Blut, das älter als sieben Tage ist, verschwindet von selbst.
- Das Staff-Team kann die Leiche zu einem zurückliegenden Todesfall nachträglich erstellen.
Video folgt in Kürze
Ein Ermittler an einem Test-Tatort: Er untersucht die Leiche, tütet eine Patronenhülse ein und sichert dann einen Fingerabdruck auf einer Karte.
assets/projects/forensics/crime-scene.mp4 (20 bis 30 Sekunden)
Screenshot folgt in Kürze
Das Panel zur Leichenuntersuchung bei einem Testcharakter, mit Todeszeit, Verletzungen und mitgeführten Waffen.
assets/projects/forensics/inspect-corpse.webp (enger Zuschnitt auf das Panel)
Video (optional) folgt in Kürze
Ein Krimineller verbrennt eine Leiche mit Benzin oder packt sie in einen Leichensack.
assets/projects/forensics/body-disposal.mp4 (etwa 15 Sekunden)
Beweise
- Fingerabdrücke bleiben auf Gegenständen zurück, wenn man sie aufhebt oder fallen lässt, und auch auf Türen, sofern man keine Handschuhe trägt.
- Abdrücke auf Gegenständen verblassen nach einer Woche, auf Türen nach einer Stunde, und dieselbe Person kann pro Gegenstand und Stunde nur einen Abdruck hinterlassen, damit sich die Gegenstände nicht mit Rauschen füllen.
- Ermittler sichern Abdrücke mit Sets und Karten, testen Hände auf Schmauchspuren, markieren Tatorte mit nummerierten Markern, Absperrband und Kreideumrissen und sammeln Patronenhülsen und Blut ein.
- Reguläre Beamte können Beweise in Beweismitteltüten versiegeln, sodass Hülsen mit intakten Abdrücken bei einem Ermittler ankommen.
- Verbrauchte Sets und leere Behälter lassen sich entsorgen, statt die Inventare zu verstopfen.
Screenshot folgt in Kürze
Eine Fingerabdruckkarte nach dem Sichern eines Abdrucks, mit ihrer bearbeiteten Trägerkarte.
assets/projects/forensics/fingerprint-card.webp (enger Zuschnitt)
Fallakten und die Datenbank
- Jede Probe landet in einer Forensik-Datenbank, die Ermittler durchsuchen, zum Abgleich nutzen und aus der sie Berichte exportieren können.
- Ermittler legen Fallakten an und hängen Beweise daran, per Fallnummer oder über einen Teil des Fallnamens.
- Fallberichte sind Gegenstände: Wird einer kopiert oder zurückgegeben, bleibt er mit dem zugrunde liegenden Fall verknüpft, und Änderungen an der Trägerkarte einer Fingerabdruckkarte werden in der Datenbank gespeichert, sodass sie das Zurückgeben und erneute Herausnehmen überstehen.
- Aktuelle Fälle werden auf dem Server berechnet, und Suchen blättern durch die Ergebnisse, damit die Listen bei wachsender Datenbank nutzbar bleiben.
Screenshot folgt in Kürze
Die Forensik-Datenbank mit Suchergebnissen, gefüllt mit Testdaten.
assets/projects/forensics/case-database.webp (enger Zuschnitt auf das Panel)
Screenshot folgt in Kürze
Eine Ermittler-Fallakte mit angehängten Beweisen, nur Testdaten.
assets/projects/forensics/case-file.webp (enger Zuschnitt auf das Panel)
Für einen Live-Server gebaut
- Das Sichern eines Abdrucks braucht eine einmalige Freigabe vom Server, die an genau diese Karte und genau diesen Gegenstand gebunden ist, nach einer Minute abläuft und nur in Reichweite funktioniert. Der Abdruck selbst wird auf dem Server nachgeschlagen und nie aus der Anfrage übernommen.
- Änderungen an einer Trägerkarte akzeptieren nur bekannte Felder, jeweils in der Länge begrenzt, und werden auf dem Server gespeichert.
- Jede Aktion prüft auf dem Server die Forensik-Stufe des Spielers, vom Zivilisten über den Ermittler bis zum Forensik-Experten.
- Tatorte werden über den gemeinsamen Plugin-Datenspeicher in MySQL gespeichert, mit einem Autosave alle fünf Minuten. Das ersetzt die Dateispeicherung, die bei Map-Wechseln Entitäten verlor.
- Ich habe interne Werkzeuge gebaut, um Tatorte schnell zu testen und um zu debuggen, wo Patronenhülsen landen.
-- Step 1, on the server: the player uses a fingerprint card while looking at an item.
-- If there's a choice of prints, the server grants a one-time authorisation tied
-- to this card and this item, and sends the list to choose from.
onRun = function(card)
local ply = card.player
local target = ply:GetEyeTrace().Entity
if (target:GetClass() ~= "nut_item") then return false end
local prints = Forensics:GetValidFingerprints(target) -- decayed prints already filtered out
-- ... zero prints and single-print shortcuts omitted
ply.authFingerprintCard = card.id
ply.authFingerprintItem = target.nutItemID
ply.authFingerprintCardTime = SysTime()
-- ... send the choices to the client
return false
end
-- Step 2: the client answers with the card and the index of the print it picked.
net.Receive("Forensics:FingerprintCard", function(_, ply)
local card = nut.item.instances[net.ReadUInt(32)]
local printIndex = net.ReadUInt(32)
local target = ply:GetEyeTrace().Entity
-- The authorisation must match this card and be under a minute old.
if (not card or card.uniqueID ~= "fingerprintcard"
or ply.authFingerprintCard ~= card.id
or SysTime() - ply.authFingerprintCardTime > 60) then
return
end
-- Still looking at the same item, and within arm's reach.
if (not IsValid(target) or target:GetClass() ~= "nut_item"
or target.nutItemID ~= ply.authFingerprintItem
or target:GetPos():DistToSqr(ply:GetPos()) > 100 * 100) then
return
end
-- The prints are looked up again on the server. The client only picks an index.
local prints = Forensics:GetValidFingerprints(nut.item.instances[target.nutItemID])
-- One use only.
ply.authFingerprintCard = nil
ply.authFingerprintItem = nil
ply.authFingerprintCardTime = nil
local chosen = prints[printIndex]
if (not chosen) then return end
-- The card must still be in this player's inventory.
if (not ply:getChar():getInv():getItems()[card.id]) then return end
ply:notify("You peel off a fingerprint and imprint it onto the card.")
-- ... write the lifted print to the forensics database (omitted)
end)Noch nicht ganz entwirrt
Ein Teil des ursprünglichen Designs liegt noch darunter und taucht gelegentlich als merkwürdiger Bug auf. Jeder Fix ist eine Gelegenheit, ein weiteres Stück davon zu ersetzen.