BeamMP-ს თამაშის რეჟიმები არ აქვს. BeamMP-ის სერვერი მანქანების მდგომარეობას გადასცემს და Lua-ს უშვებს, და ყველაფერი, რაც სერვერს ავტოსადგომზე მეტად აქცევს - რბოლები, whitelist-ები, მანქანების წესები, ჩატის ბრძანებები, Discord-ის ხიდი - არის Lua plugin Resources/Server-ში. სერვერის მხარის API იმდენად პატარაა, რომ ერთ საღამოში ისწავლება: არეგისტრირებ დამმუშავებლებს ათიოდე ივენთისთვის, იძახებ ორ ათეულამდე MP.* ფუნქციას და იყენებ ტაიმერებს ყველაფრისთვის, რაც დროში ხდება. ეს ნამდვილი რბოლების ჩასატარებლადაც საკმარისია, რადგან სერვერს შეუძლია MP.GetPositionRaw-ით წაიკითხოს ყოველი მანქანის ბოლოს გაგზავნილი პოზიცია, და საკონტროლო წერტილების სისტემას მეტი არაფერი სჭირდება. ეს გზამკვლევი მოიცავს plugin-ების მოდელს, ივენთებსა და ფუნქციებს, რომლებსაც რეალურად გამოიყენებ, და სრულ სარბოლო plugin-ს უკუთვლით, კარიბჭეებით, წრეებით და ფინიშის დროებით.
BeamMP-ის სერვერის გზამკვლევი მოიცავს ინსტალაციას, ServerConfig.toml-ს, რუკებსა და კლიენტის მოდებს; თუ შენი სერვერი ჯერ არ მუშაობს, ჯერ ის წაიკითხე. აქ ყველაფერი 3.x სერვერისთვისაა, რომელიც Lua 5.3-ს იყენებს. 2.x API-სთვის ან დიდი ხნის წინ მიტოვებული საზოგადოების ფრეიმვორკებისთვის დაწერილი ძველი plugin-ები ხშირად არ იტვირთება, და ეს პირველია, რაც უნდა შეამოწმო, როცა ჩამოტვირთული სკრიპტი არაფერს აკეთებს.
როგორ იტვირთება სერვერის plugin-ები#
Plugin არის საქაღალდე Resources/Server-ის ქვეშ. სერვერი ტვირთავს ამ საქაღალდის ძირში არსებულ ყველა .lua ფაილს ანბანური რიგით და ზედა დონის კოდს ერთხელ უშვებს. ქვესაქაღალდეებში არსებული ფაილები იგნორირდება, თუ მათ require-ით არ ჩატვირთავ, და სწორედ იქ არის ადგილი დამხმარე ფუნქციებისა და მონაცემებისთვის.
Resources/ Client/ mod zips sent to players Server/ RaceControl/ main.lua loaded at start lib/gates.lua only if main.lua requires it Whitelist/ main.luaყოველი plugin საკუთარ Lua state-ში მუშაობს. ორ plugin-ს ერთმანეთის ცვლადების წაკითხვა არ შეუძლია; ისინი ივენთებით ურთიერთობენ, რაც ქვემოთაა განხილული. plugin-ის შიგნით სქემა ყოველთვის ერთია: განსაზღვრე გლობალური ფუნქციები, შემდეგ კი დაარეგისტრირე ისინი ივენთების სახელებზე MP.RegisterEvent(eventName, functionName)-ით. მეორე არგუმენტი გლობალური ფუნქციის სახელია სტრიქონის სახით და არა თავად ფუნქცია, ამიტომაც local-ად გამოცხადებული დამმუშავებელი ჩუმად არასდროს ეშვება.
სერვერი plugin-ის საქაღალდესაც უთვალთვალებს. plugin-ის ძირში ფაილის შენახვა onFileChanged-ს იწვევს და ამ plugin-ის state-ს ხელახლა ტვირთავს, ასე რომ, სერვერის გადატვირთვის გარეშე შეგიძლია იმუშაო. ხელახალი ჩატვირთვა plugin-ის ცვლადებს შლის, ამიტომ მიმდინარე რბოლა ქრება, როცა main.lua-ს შეინახავ; არედაქტირე რბოლებს შორის და არა მათ დროს.
გამონატანი სერვერის კონსოლში მიდის. print წერს [LUA] პრეფიქსით, ხოლო Util.LogInfo, Util.LogWarn, Util.LogError და Util.LogDebug შესაბამის დონეზე წერენ. სინტაქსური შეცდომა ჩატვირთვისას ფაილითა და ხაზით ცხადდება, და plugin გამოტოვებულია. თუ plugin „არაფერს აკეთებს", პასუხი კონსოლშია, ჩატვირთვის მომენტიდან - კონსოლის კითხვა აღწერს, როგორ გააკეთო ეს ეფექტურად.
ივენთები, რომლებსაც გამოიყენებ#
| ივენთი | არგუმენტები | გაუქმებადი |
|---|---|---|
onInit | არცერთი | არა |
onPlayerAuth | name, role, is_guest, identifiers | კი - დააბრუნე 1 ან მიზეზის სტრიქონი |
onPlayerConnecting | player_id | არა |
onPlayerJoining | player_id | არა |
onPlayerJoin | player_id | არა |
onPlayerDisconnect | player_id | არა |
onChatMessage | player_id, player_name, message | კი - დააბრუნე 1 შეტყობინების დასამალად |
onVehicleSpawn | player_id, vehicle_id, data | კი - დააბრუნე 1 spawn-ზე უარის სათქმელად |
onVehicleEdited | player_id, vehicle_id, data | კი - დააბრუნე 1 ცვლილებაზე უარის სათქმელად |
onVehicleReset | player_id, vehicle_id, data | არა |
onVehicleDeleted | player_id, vehicle_id | არა |
onConsoleInput | input | არა |
onShutdown | არცერთი | არა |
ამათგან სამი აკეთებს სამუშაოს უმეტესობას. onPlayerAuth ეშვება მანამ, სანამ მოთამაშეს ID ექნება, ამიტომ whitelist-ები და ბანები აქ ცხოვრობს; ის იღებს identifiers ცხრილს მოთამაშის beammp ID-ით, IP-ით და, თუ მიბმულია, Discord ID-ით. onChatMessage-ით ხორციელდება ჩატის ყოველი ბრძანება - ბრძანებების რეესტრი არ არსებობს, ტექსტს თავად არჩევ. onVehicleSpawn და onVehicleEdited მანქანების წესების ადგილია: data არგუმენტი მანქანის კონფიგურაციას JSON-ად შეიცავს, ასე რომ, შეგიძლია უარი თქვა მოდელზე, ნაწილზე ან პიტების გარეთ spawn-ზე.
გაუქმება დაბრუნებული მნიშვნელობით ხდება და მხოლოდ მისით. onChatMessage-იდან 1-ის დაბრუნება შეტყობინებას ყველასგან მალავს, რაც ბრძანებებისთვის გინდა - /start-ის ნახვა ჩატში არავის სჭირდება. თუ არაფერს დააბრუნებ, შეტყობინება გაივლის.
ფუნქციები, რომელთა ცოდნაც ღირს#
| ფუნქცია | რას აკეთებს |
|---|---|
MP.SendChatMessage(id, text) | ჩატი ერთ მოთამაშეს, ან ყველას -1-ით |
MP.GetPlayers() | ცხრილი: მოთამაშის ID - სახელი |
MP.GetPlayerName(id) | სახელი ID-ის მიხედვით |
MP.GetPlayerIdentifiers(id) | beammp, ip და discord, თუ ცნობილია |
MP.IsPlayerGuest(id) | აქვს თუ არა მოთამაშეს BeamMP ანგარიში |
MP.GetPlayerVehicles(id) | ცხრილი: მანქანის ID - მანქანის მონაცემები |
MP.GetPositionRaw(id, vid) | ბოლოს გაგზავნილი pos, rot, vel, rvel, ping და tim |
MP.RemoveVehicle(id, vid) | შლის მანქანას |
MP.DropPlayer(id, reason) | აგდებს შეტყობინებით |
MP.CreateEventTimer(name, ms) | იწვევს ივენთს name ყოველ ms მილიწამში |
MP.CancelEventTimer(name) | აჩერებს მას |
MP.TriggerLocalEvent(name, ...) | იძახებს დამმუშავებლებს ამ plugin-ში, სინქრონულად |
MP.TriggerGlobalEvent(name, ...) | იძახებს დამმუშავებლებს ყველა plugin-ში |
MP.TriggerClientEvent(id, name, data) | უგზავნის სტრიქონს კლიენტის მხარის მოდს |
MP.Set(MP.Settings.MaxCars, n) | ცვლის კონფიგურაციის მნიშვნელობას გადატვირთვამდე |
MP.CreateTimer() | წამზომი :Start()-ით და :GetCurrent()-ით წამებში |
პოზიციები ვექტორებია Lua-ს მასივებში, ასე რომ, raw.pos[1], raw.pos[2] და raw.pos[3] არის x, y და z. BeamNG z-ს ზემოთ მიმართულებად იყენებს, ამიტომ მიწის სიბრტყე x და y-ია. Util.JsonEncode და Util.JsonDecode ცხრილებს სტრიქონებად აქცევს და პირიქით, რაც გჭირდება როგორც მონაცემების შესანახად, ისე კლიენტის მოდებთან სალაპარაკოდ. FS.Exists, FS.CreateDirectory და FS.ConcatPaths ფაილებსა და გზებს პლატფორმისგან დამოუკიდებლად ამუშავებს.
სიგნატურები სერვერის ვერსიებს შორის იცვლება, და შენი ვერსიის ოფიციალური სკრიპტინგის ცნობარი ჯობია ბლოგის ნებისმიერ ცხრილს, ამის ჩათვლით.
ჩატის ბრძანებები და ადმინები#
ჩაშენებული ადმინების სია არ არსებობს, ამიტომ plugin წყვეტს, ვინ არის ადმინი. დააფუძნე ეს beammp იდენტიფიკატორზე და არასდროს ნაჩვენებ სახელზე, რომლის შეცვლაც მოთამაშეებს შეუძლიათ:
local ADMINS = { ["1234567"] = true }local function isAdmin(pid) local ids = MP.GetPlayerIdentifiers(pid) return ids ~= nil and ADMINS[tostring(ids.beammp)] == trueendfunction AdminChat(pid, name, msg) local cmd, arg = msg:match("^/(%S+)%s*(.*)$") if not cmd then return end if not isAdmin(pid) then return end if cmd == "kick" then for id, pname in pairs(MP.GetPlayers()) do if pname == arg then MP.DropPlayer(id, "Kicked by " .. name) end end elseif cmd == "cars" then MP.Set(MP.Settings.MaxCars, tonumber(arg) or 1) MP.SendChatMessage(-1, "Car limit is now " .. arg) else return end return 1endMP.RegisterEvent("onChatMessage", "AdminChat")შაბლონი ^/(%S+)%s*(.*)$ ყოფს /kick SomeName-ს ბრძანებად და ხაზის დანარჩენ ნაწილად. უცნობი ბრძანებები გაივლის და ჩატში ჩნდება, რაც უფრო მეგობრული ჩავარდნაა. MP.Set პარამეტრს მხოლოდ გაშვებული სერვერისთვის ცვლის; ServerConfig.toml-ის მნიშვნელობა შემდეგი გადატვირთვისას ბრუნდება, რაც მას ივენთების საღამოებისთვის იდეალურს ხდის.
მანქანების წესები spawn-ისას#
onVehicleSpawn-ის data არგუმენტი სტრიქონია, რომელსაც JSON-ის წინ პრეფიქსი აქვს, ამიტომ დეკოდირებამდე პირველი ფიგურული ფრჩხილიდან მოჭერი. მის მიმართ წესების დაწერამდე ერთი spawn ჩაწერე ლოგში - ველების სახელები თამაშიდან მოდის, და მათი გაგების ყველაზე უსაფრთხო გზა ნახვაა:
local ALLOWED = { covet = true, sunburst = true }function CarSpawn(pid, vid, data) local start = data:find("{") if not start then return end local cfg = Util.JsonDecode(data:sub(start)) print("spawn by " .. MP.GetPlayerName(pid) .. ": " .. tostring(cfg.jbm)) if cfg.jbm and not ALLOWED[cfg.jbm] then MP.SendChatMessage(pid, "Only the race cars are allowed tonight.") return 1 endendMP.RegisterEvent("onVehicleSpawn", "CarSpawn")MP.RegisterEvent("onVehicleEdited", "CarSpawn")იგივე დამმუშავებელი onVehicleEdited-ზეც დაარეგისტრირე, თორემ მოთამაშეები დაშვებულ მანქანას გამოიძახებენ და შემდეგ მანქანის კონფიგურატორში გაცვლიან. უარყოფილი spawn სხვა მოთამაშეების ეკრანებზე არაფერს ტოვებს; შემდეგ MP.RemoveVehicle-ით წაშლილი მანქანა კი ჯერ წამიერად ჩნდება, ამიტომ, სადაც შეგიძლია, უარი spawn-ისას თქვი.
საკონტროლო წერტილების სარბოლო plugin#
რბოლებს ოთხი რამ სჭირდება: მონაწილეების სია, უკუთვლა, გზა იმის გასაგებად, როდის გაივლის მანქანა წერტილს, და საათი. სერვერს ოთხივე აქვს. კარიბჭეები რუკაზე წერტილებია რადიუსით; ყოველმა მრბოლელმა ისინი რიგრიგობით უნდა გაიაროს, და ბოლო კარიბჭის გავლა წრეს ასრულებს. პოზიციები ტაიმერით მოწმდება.
local ADMINS = { ["1234567"] = true }local GATES = { -- fill in with /pos, finish line last { x = 0.0, y = 0.0, r = 15 }, { x = 0.0, y = 0.0, r = 15 },}local LAPS = 3local race = { state = "idle", count = 0, racers = {}, finished = 0 }local clock = MP.CreateTimer()local function say(pid, text) MP.SendChatMessage(pid, "[Race] " .. text) endlocal function isAdmin(pid) local ids = MP.GetPlayerIdentifiers(pid) return ids ~= nil and ADMINS[tostring(ids.beammp)] == trueendlocal function firstVehicle(pid) local vehicles = MP.GetPlayerVehicles(pid) if vehicles == nil then return nil end for vid, _ in pairs(vehicles) do return vid endendlocal function reset() MP.CancelEventTimer("RaceCountdown") MP.CancelEventTimer("RaceTick") race = { state = "idle", count = 0, racers = {}, finished = 0 }endfunction RaceChat(pid, name, msg) local cmd = msg:match("^/(%S+)") if cmd == "join" and race.state == "idle" then race.racers[pid] = { gate = 1, lap = 1, done = false } say(-1, name .. " is on the grid") elseif cmd == "pos" then local vid = firstVehicle(pid) local raw = vid and MP.GetPositionRaw(pid, vid) if raw and raw.pos then say(pid, string.format("x=%.1f y=%.1f", raw.pos[1], raw.pos[2])) end elseif cmd == "start" and isAdmin(pid) and race.state == "idle" then race.state = "countdown" race.count = 5 MP.CreateEventTimer("RaceCountdown", 1000) elseif cmd == "stop" and isAdmin(pid) then reset() say(-1, "Race cancelled") else return end return 1endfunction RaceCountdown() if race.count > 0 then say(-1, tostring(race.count)) race.count = race.count - 1 return end MP.CancelEventTimer("RaceCountdown") race.state = "running" clock:Start() say(-1, "GO") MP.CreateEventTimer("RaceTick", 200)endfunction RaceTick() for pid, r in pairs(race.racers) do local vid = firstVehicle(pid) local raw = vid and MP.GetPositionRaw(pid, vid) if raw and raw.pos and not r.done then local g = GATES[r.gate] local dx, dy = raw.pos[1] - g.x, raw.pos[2] - g.y if dx * dx + dy * dy <= g.r * g.r then r.gate = r.gate + 1 if r.gate > #GATES then r.gate = 1 if r.lap >= LAPS then r.done = true race.finished = race.finished + 1 say(-1, string.format("P%d %s %.2fs", race.finished, MP.GetPlayerName(pid), clock:GetCurrent())) else r.lap = r.lap + 1 end end end end endendfunction RaceLeave(pid) race.racers[pid] = nil endMP.RegisterEvent("onChatMessage", "RaceChat")MP.RegisterEvent("RaceCountdown", "RaceCountdown")MP.RegisterEvent("RaceTick", "RaceTick")MP.RegisterEvent("onPlayerDisconnect", "RaceLeave")დასაყენებლად ტრასის თითოეულ წერტილამდე მიდი - რამდენიმე ასეული მეტრის დაშორებით, ისეთ მოსახვევებში, რომლებსაც ვერავინ მოჭრის - და აკრიფე /pos. დააკოპირე x და y GATES-ში სვლის თანმიმდევრობით, სტარტ-ფინიშის ხაზი კი ბოლო ჩანაწერად ჩასვი, რომ გრიდიდან დაძრული მანქანები ჯერ პირველი კარიბჭისკენ წავიდნენ. შეინახე ფაილი, plugin ხელახლა ჩაიტვირთება, და მრბოლელები /join-ს კრეფენ, სანამ ადმინი /start-ს აკრეფს.
რამდენად ზუსტია ეს, გულწრფელად
სერვერმა მხოლოდ ის იცის, სად იყო მანქანა ბოლო განახლებისას, ამიტომ სიზუსტე ორ რამეზეა დამოკიდებული. ყოველ 200 ms-ში შემოწმება ნიშნავს, რომ 200 კმ/სთ-ზე მიმავალი მანქანა შემოწმებებს შორის დაახლოებით 11 მეტრს გადის, ამიტომაც არის კარიბჭის რადიუსი 15 მეტრი და არა სამი. და ყოველი პოზიცია ისეთივე ძველია, როგორც ამ მოთამაშის ქსელური დაყოვნება, ასე რომ, 150 ms ping-ის მქონე მძღოლის დრო ოდნავ გვიან ითვლება. პრაქტიკაში ეს წამის რამდენიმე მეათედია, რაც მხიარული კვირის რბოლისთვის ნორმალურია და არა ჩემპიონატისთვის, რომელიც მეასედებით წყდება.
მისი გამკაცრების ორი გზა არსებობს. tick-ის 100 ms-მდე შემცირება მანძილის პრობლემას ორჯერ ამცირებს და სამუშაოს ორჯერ ზრდის; რამდენიმე მრბოლელით ეს ფასი უმნიშვნელოა, ორმოცით - არა. ნამდვილი სიზუსტისთვის ამოცნობა ყოველი მოთამაშის საკუთარ მანქანაზე უნდა მოხდეს: კლიენტის მხარის მოდი Resources/Client-ში ლოკალურად ადევნებს თვალს კარიბჭის გადაკვეთას და TriggerServerEvent-ით აცნობებს, სერვერის plugin კი მათ MP.RegisterEvent-ით იღებს დამმუშავებლით, რომელიც მოთამაშის ID-სა და მონაცემების სტრიქონს იღებს. ეს მეტი სამუშაოა და კლიენტის მოდს მოითხოვს, რომელსაც შენ შეინახავ, ამიტომაც საზოგადოების სარბოლო სერვერების უმეტესობა უბრალო ივენთებისთვის სერვერის მხარის მარტივ მიდგომას იყენებს, ლიგებისთვის კი სპეციალურ timing მოდს.
როგორ აქციო რბოლა ნამდვილ რბოლად
რამდენიმე დამატება, რომელიც ცოტა ჯდება:
- რბოლამდე გაყინე შემადგენლობა:
MP.Set-ითMaxCarsდააყენე1-ზე, რომ რბოლის შუაში არავინ გამოიძახოს მეორე მანქანა, და შემდეგ აღადგინე. - აკრძალე reset-ები რბოლის დროს.
onVehicleResetგაუქმებადი არ არის, მაგრამ დისკვალიფიკაცია შეგიძლია: თუ მრბოლელი reset-ს აკეთებს, როცაrace.stateარისrunning, დააყენეdoneდა გამოაცხადე. - შედეგები Discord-ში გამოაცხადე: ჩაწერე ისინი ფაილში, რომელსაც ცალკე ბოტი კითხულობს, ან გამოიყენე webhook-ებისთვის შექმნილი plugin - Discord webhook-ები სერვერის სტატუსისთვის მიმღებ მხარეს აღწერს.
- ტრასის კონფიგურაციები მონაცემებად შეინახე. ყოველი ტრასის კარიბჭეები
lib/-ში ცალკე ფაილში ჩადე და სწორიrequire-ით ჩატვირთე, რომ ადგილის შეცვლა plugin-ის რედაქტირებას არ ნიშნავდეს.
მონაცემების შენახვა გადატვირთვებს შორის#
Plugin-ის ცვლადები სერვერთან ერთად კვდება. ყველაფერი, რაც უნდა გადარჩეს - წრის რეკორდები, ბანების სია, რბოლების ისტორია - დისკზე უნდა ჩაიწეროს. Lua-ს io ბიბლიოთეკა და Util.JsonEncode საკმარისია:
local DIR = "Resources/Server/RaceControl/data"local FILE = FS.ConcatPaths(DIR, "records.json")local function loadRecords() if not FS.Exists(FILE) then return {} end local f = io.open(FILE, "r") local text = f:read("*a") f:close() return Util.JsonDecode(text) or {}endlocal function saveRecords(records) if not FS.Exists(DIR) then FS.CreateDirectory(DIR) end local f = io.open(FILE, "w") f:write(Util.JsonEncode(records)) f:close()endგზები სერვერის სამუშაო დირექტორიის მიმართაა. plugin-ის საკუთარ ძირეულ საქაღალდეში ფაილის ჩაწერამ შეიძლება ამ plugin-ის hot reload გამოიწვიოს, რაც წაშლის რბოლას, რომელმაც ახლახან რეკორდი ჩაწერა - ამიტომ მონაცემების ფაილები ჩადე ქვესაქაღალდეში, მაგალითად data/-ში, როგორც ზემოთაა, რომელსაც სერვერი plugin-ის კოდად არ ტვირთავს. ეს BeamMP-ის plugin-ების შემუშავებაში ყველაზე დამაბნეველი ხარვეზია და ისე გამოიყურება, თითქოს სერვერი plugin-ს შემთხვევით გადატვირთავს.
ეს მონაცემების ფაილები შენი სერვერის ის ნაწილია, რომელსაც ხელახლა ვერ ჩამოტვირთავ. Resources/Server-ის backup გააკეთე რედაქტირების ყოველი სესიის წინ და სერვერის ყოველი განახლების წინ; backup-ები, რომლებიც მართლა აღდგება არის არგუმენტი იმისთვის, რომ ერთი მართლა გამოცადო.
კომუნიკაცია plugin-ებს შორის და კლიენტის მოდებთან#
Plugin-ები იზოლირებულია, ამიტომ whitelist plugin-ს სარბოლო plugin-ის ფუნქციის გამოძახება არ შეუძლია. მათ ივენთები აკავშირებს: MP.TriggerGlobalEvent("RaceFinished", pid, time) ერთ plugin-ში იძახებს RaceFinished-ზე დარეგისტრირებულ ყველა დამმუშავებელს ყველა plugin-ში. MP.TriggerLocalEvent იგივეს აკეთებს ერთი plugin-ის შიგნით და სინქრონულად ბრუნდება, რაც დიდი plugin-ის ფაილებად დასაყოფად მოსახერხებელია.
კლიენტის მოდები საპირისპირო მიმართულებაა. Resources/Client-ში არსებული zip შეიძლება შეიცავდეს Lua-ს, რომელიც ყოველი მოთამაშის თამაშში ეშვება, AddEventHandler-ით არეგისტრირებს დამმუშავებლებს და TriggerServerEvent-ით აგზავნის მონაცემებს. სერვერი აგზავნის MP.TriggerClientEvent(pid, name, data)-ით, ან ცხრილისთვის MP.TriggerClientEventJson-ით. ყველაფერი სტრიქონებად მოგზაურობს, ამიტომ ცხრილები ორივე მხარეს JSON-ად დაშიფრე. გამოიყენე ეს ინტერფეისისთვის - უკუთვლა ეკრანზე ჩატის ნაცვლად, წრის ტაიმერი, ლიდერბორდი - ხოლო ავტორიტეტი სერვერზე დატოვე: კლიენტის მოდი, რომელიც საკუთარ წრის დროებს აცხადებს, შეიძლება გაასწოროს მოთამაშემ, რომელიც მას უშვებს.
უსაფრთხოება, წარმადობა და plugin-ების მუშა მდგომარეობაში შენარჩუნება#
Plugin-ები სერვერის სრული უფლებებით მუშაობს და ხედავს ჩატის ყოველ ხაზსა და ყოველ შეერთებას. რასაც ჩამოტვირთავ, წაიკითხე, სანამ ავტომატურად ჩატვირთვად საქაღალდეში ჩააგდებ, და უპირატესობა მიანიჭე plugin-ებს საჯარო კოდის რეპოზიტორიით. მოდიფიცირებული სერვერის სისუფთავე ზოგადი ჰიგიენაა, ხოლო მავნე plugin-ები და მოდები აღწერს, რისი გაკეთება შეუძლია მტრულ plugin-ს.
BeamMP-ში წარმადობის პრობლემები თითქმის ყოველთვის არის დამმუშავებელი, რომელიც ზედმეტად ბევრს და ზედმეტად ხშირად აკეთებს. 50 ms-იანი ტაიმერი, რომელიც ყველა მოთამაშეზე გადის და JSON-ს დეკოდირებს, მოულოდნელ CPU-დ გამოჩნდება. Util.DebugExecutionTime() თითოეული დამმუშავებლის დროის სტატისტიკას აბრუნებს, რაც ვარაუდს რიცხვად აქცევს. დამმუშავებლის შიგნით MP.Sleep დიდი ხნით არასდროს გამოიძახო; ის ამ plugin-ს ბლოკავს.
RE:NODE-ზე BeamMP-ის გეგმა გაძლევს კონსოლს ისტორიით plugin-ის გამონატანის წასაკითხად, ფაილ-მენეჯერის რედაქტორს სინტაქსის მონიშვნით main.lua-სთვის, და SFTP-ს, თუ ლოკალურად მუშაობა გირჩევნია. plugin-ის ფაილის შენახვა მას hot-reload-ით ტვირთავს, ასე რომ, რედაქტირება-შემოწმების ციკლს გადატვირთვა არ სჭირდება, ხოლო რედაქტირების სესიამდე აღებული backup-ის სლოტი მთელი Resources საქაღალდის „გაუქმების" ღილაკია.
BeamMP-ის სერვერის განახლების შემდეგ, plugin-ებით სავსე სერვერის გადატვირთვამდე, changelog-ში სკრიპტინგის ცვლილებები წაიკითხე. რა ვქნა, როცა მოდის განახლება რამეს ამტვრევს აღდგენის თანმიმდევრობაა, თუ ამას მწარე გამოცდილებით გაიგებ.
პრობლემების მოგვარება#
დამმუშავებელი არასდროს ეშვება. ფუნქცია local-ია, MP.RegisterEvent-ში არასწორად წერია, ან ივენთის სახელს არასწორი რეგისტრი აქვს. ივენთების სახელები რეგისტრისადმი მგრძნობიარეა.
Plugin შემთხვევით თავისით ირესეტება. ის ფაილს საკუთარ ძირეულ საქაღალდეში წერს და hot reload-ს იწვევს. გადაიტანე მონაცემები ქვესაქაღალდეში.
`attempt to index a nil value` პოზიციებზე. მოთამაშეს მანქანა არ აქვს, ან MP.GetPositionRaw-მა ცარიელი შედეგი დააბრუნა. გამოყენებამდე შეამოწმე raw და raw.pos.
ბრძანებები ჩატში ჩნდება. დამმუშავებელი დამუშავებული ბრძანებებისთვის 1-ს არ აბრუნებს.
კარიბჭეები არასდროს ირთვება. კოორდინატები x და z-ად დაკოპირდა x და y-ის ნაცვლად, ან რადიუსი იმ მანძილზე ნაკლებია, რომელსაც მანქანა შემოწმებებს შორის გადის.
ძველი plugin არაფერს აკეთებს. ის 2.x API-ს ეყრდნობა. მოძებნე MP.RegisterEvent დამმუშავებლების სტრიქონული სახელებით; ყველაფერს სხვას გადაწერა სჭირდება.
FAQ#
აქვს BeamMP-ს ჩაშენებული სარბოლო რეჟიმები?
არა. სერვერს თამაშის რეჟიმები საერთოდ არ აქვს. რბოლები, დროის აღრიცხვა და ლიდერბორდები Lua plugin-ებია, თავად დაწერილი ან ჩამოტვირთული, ზოგჯერ კლიენტის მხარის მოდით ეკრანზე დროის საჩვენებლად.
Lua-ს რომელ ვერსიას იყენებს BeamMP-ის სერვერი?
Lua 5.3-ს 3.x სერვერზე. ძველი 2.x API-სთვის დაწერილ plugin-ებს გადაწერა სჭირდება, ძირითადად სტრიქონზე დაფუძნებულ MP.RegisterEvent დამმუშავებლებზე და ივენთების მიმდინარე სახელებზე.
მოთამაშეებს სერვერის plugin-ებისთვის რამის დაყენება სჭირდებათ?
Resources/Server-ში არსებული სერვერის მხარის plugin-ებისთვის - არა. მოთამაშეებამდე მხოლოდ Resources/Client-ში არსებული კლიენტის მოდები აღწევს, და ისინი შემოსვლისას ავტომატურად ჩამოიტვირთება.
რამდენად ზუსტია სერვერის მხარის წრის დროის აღრიცხვა?
წამის რამდენიმე მეათედის ფარგლებში, რასაც შემოწმების ინტერვალი და ყოველი მოთამაშის ping ზღუდავს. უბრალო რბოლებისთვის ეს ნორმალურია. შეჯიბრებით დროის აღრიცხვას კლიენტზე ამოცნობა სჭირდება, რომელიც სერვერს ეცნობება.
შემიძლია plugin-ით პაროლის დამატება?
პაროლის ველი არ არსებობს, მაგრამ onPlayerAuth-ზე შეგიძლია უარი უთხრა ყველას, ვისი beammp იდენტიფიკატორიც შენს სიაში არ არის, რაც პაროლზე მკაცრია, რადგან მისი გაზიარება შეუძლებელია.
plugin-ის რედაქტირების შემდეგ სერვერის გადატვირთვა მჭირდება?
არა. plugin-ის ძირეულ საქაღალდეში ფაილის შენახვა ამ plugin-ს ხელახლა ტვირთავს. მისი ცვლადები ნულდება, ამიტომ ეს ივენთებს შორის გააკეთე და არა მათ დროს.




კომენტარები
სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.